map (keyPath) wobei keyPath eine Variable ist

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

Funktioniert super. Der folgende Code wird jedoch nicht kompiliert:

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

Der Wert vom Typ 'WritableKeyPath <(Int, Int), Int>' kann nicht in den erwarteten Argumenttyp '((Int, Int)) umgewandelt werden. Throws -> T'.
Der generische Parameter 'T' konnte nicht abgeleitet werden.

Antworten

4 NewDev Sep 06 2020 at 20:41

Array.maperwartet einen Abschluss mit Unterschrift (Element) throws -> T.

In Swift 5.2 durften Schlüsselpfade als Funktionen / Abschlüsse übergeben werden (hier ist ein Evolutionsvorschlag ), aber nur als Literale (zumindest laut Vorschlag heißt es "vorerst", sodass diese Einschränkung möglicherweise aufgehoben würde ).

Um dies zu überwinden, können Sie eine Erweiterung erstellen Sequence, die einen Schlüsselpfad akzeptiert:

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

(Gutschrift an: https://www.swiftbysundell.com/articles/the-power-of-key-paths-in-swift/)

Dann können Sie tun, was Sie wollten:

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

Der Evolutionsvorschlag zeigte, wie dies mit einem Operator gemacht wird. Sie können jedoch auch dieselbe []oder dieselbe ()Syntax verwenden, unabhängig davon, ob sie teilweise angewendet wird oder nicht, da Indizes und Funktionen keine Argumente benötigen.

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
  }
}