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

Works great. But the below code doesn't compile:

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

Cannot convert value of type 'WritableKeyPath<(Int, Int), Int>' to expected argument type '((Int, Int)) throws -> T'.
Generic parameter 'T' could not be inferred.

Roman
  • 1,309
  • 14
  • 23

2 Answers2

5

Array.map expects a closure with signature (Element) throws -> T.

In Swift 5.2, key paths were allowed to be passed in as functions/closures (here's an evolution proposal), but only as literals (at least, according to the proposal, it says "for now", so perhaps this restriction would be lifted).

To overcome this, you can create an extension on Sequence that accepts a key path:

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

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

Then you can do what you wanted:

let keyPath = \(Int, Int).0
arr.map(keyPath)
New Dev
  • 48,427
  • 12
  • 87
  • 129
  • 2
    Also according to that same evolution proposal, you could write `let transform: ((Int, Int)) -> Int = \.0` and then `arr.map(transform)`. – vacawama Sep 06 '20 at 14:25
0

The evolution proposal showed how to do it with an operator, but you can also use the same [] or () syntax whether it's partially-applied or not, because subscripts and functions don't need arguments.

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