map (keyPath), где keyPath - это переменная

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

Работает отлично. Но приведенный ниже код не компилируется:

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

Невозможно преобразовать значение типа WritableKeyPath <(Int, Int), Int> в ожидаемый тип аргумента, выбрасываемое ((Int, Int)) -> T.
Не удалось вывести общий параметр "T".

Ответы

4 NewDev Sep 06 2020 at 20:41

Array.mapожидает закрытия с подписью (Element) throws -> T.

В Swift 5.2 ключевые пути можно было передавать как функции / замыкания (вот предложение эволюции ), но только как литералы (по крайней мере, согласно предложению, он говорит «пока», так что, возможно, это ограничение будет снято. ).

Чтобы преодолеть это, вы можете создать расширение, Sequenceкоторое принимает ключевой путь:

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

(кредит: https://www.swiftbysundell.com/articles/the-power-of-key-paths-in-swift/)

Тогда вы можете делать то, что хотели:

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

Предложение эволюция показала , как сделать это с оператором, но вы также можете использовать тот же []или ()синтаксис ли он частично применяется или нет, так как индексы и функции не нужны аргументы.

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