map (keyPath) di mana keyPath adalah variabel

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

Bekerja dengan baik. Tetapi kode di bawah ini tidak dapat dikompilasi:

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

Tidak dapat mengonversi nilai tipe 'WritableKeyPath <(Int, Int), Int>' menjadi tipe argumen yang diharapkan '((Int, Int)) throws -> T'.
Parameter umum 'T' tidak dapat disimpulkan.

Jawaban

4 NewDev Sep 06 2020 at 20:41

Array.mapmengharapkan penutupan dengan tanda tangan (Element) throws -> T.

Di Swift 5.2, jalur kunci diizinkan untuk diteruskan sebagai fungsi / penutupan (ini adalah proposal evolusi ), tetapi hanya sebagai literal (setidaknya, menurut proposal, dikatakan "untuk saat ini", jadi mungkin pembatasan ini akan dicabut ).

Untuk mengatasinya, Anda dapat membuat ekstensi di Sequenceyang menerima jalur kunci:

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

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

Kemudian Anda dapat melakukan apa yang Anda inginkan:

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

Proposal evolusi menunjukkan cara melakukannya dengan operator, tetapi Anda juga dapat menggunakan sintaks yang sama []atau ()apakah diterapkan sebagian atau tidak, karena subskrip dan fungsi tidak memerlukan argumen.

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