map (keyPath) donde keyPath es una variable

Sep 06 2020
let arr = [(1, 1), (2, 2), (3, 3), (4, 4), (5, 5)]
arr.map(\.0) // [1, 2, 3, 4, 5]

Funciona genial. Pero el siguiente código no se compila:

let keyPath = \(Int, Int).0
arr.map(keyPath)

No se puede convertir el valor del tipo 'WritableKeyPath <(Int, Int), Int>' al tipo de argumento esperado '((Int, Int)) arroja -> T'.
No se pudo inferir el parámetro genérico 'T'.

Respuestas

4 NewDev Sep 06 2020 at 20:41

Array.mapespera un cierre con firma (Element) throws -> T.

En Swift 5.2, se permitía que las rutas clave se pasaran como funciones / cierres (aquí hay una propuesta de evolución ), pero solo como literales (al menos, según la propuesta, dice "por ahora", por lo que tal vez se levante esta restricción ).

Para superar esto, puede crear una extensión Sequenceque acepte una ruta clave:

extension Sequence {
   func map<T>(_ keyPath: KeyPath<Element, T>) -> [T] {
      return map { $0[keyPath: keyPath] }
   }
}

(crédito a: https://www.swiftbysundell.com/articles/the-power-of-key-paths-in-swift/)

Entonces puedes hacer lo que quisieras:

let keyPath = \(Int, Int).0
arr.map(keyPath)
Jessy Sep 06 2020 at 22:24

La propuesta de evolución mostró cómo hacerlo con un operador, pero también se puede usar la misma sintaxis []o ()si se aplica parcialmente o no, porque los subíndices y las funciones no necesitan argumentos.

let oneTo5 = 1...5
let keyPath = \(Int, Int).0
XCTAssert(
  zip(oneTo5, oneTo5).map(keyPath[]).elementsEqual(oneTo5)
)
let keyPath = \Double.isZero
XCTAssertFalse(keyPath[1.0]())
public extension KeyPath {
  /// Convert a `KeyPath` to a partially-applied get accessor.
  subscript() -> (Root) -> Value {
    { $0[keyPath: self] } } /// Convert a `KeyPath` to a get accessor. subscript(root: Root) -> () -> Value { { root[keyPath: self] } } } public extension ReferenceWritableKeyPath { /// Convert a `KeyPath` to a partially-applied get/set accessor pair. subscript() -> (Root) -> Computed<Value> { { self[$0] }
  }

  /// Convert a `KeyPath` to a get/set accessor pair.
  subscript(root: Root) -> Computed<Value> {
    .init(
      get: self[root],
      set: { root[keyPath: self] = $0 }
    )
  }
}


/// A workaround for limitations of Swift's computed properties.
///
/// Limitations of Swift's computed property accessors:
/// 1. They are not mutable.
/// 2. They cannot be referenced as closures.
@propertyWrapper public struct Computed<Value> {
  public typealias Get = () -> Value
  public typealias Set = (Value) -> Void

  public init(
    get: @escaping Get,
    set: @escaping Set
  ) {
    self.get = get
    self.set = set
  }

  public var get: Get
  public var set: Set

  public var wrappedValue: Value {
    get { get() }
    set { set(newValue) }
  }

  public var projectedValue: Self {
    get { self }
    set { self = newValue }
  }
}

//MARK:- public
public extension Computed {
  init(
    wrappedValue: Value,
    get: @escaping Get = {
      fatalError("`get` must be assigned before accessing `wrappedValue`.")
    },
    set: @escaping Set
  ) {
    self.init(get: get, set: set)
    self.wrappedValue = wrappedValue
  }
}