Search code examples
swiftswift5keypaths

map(keyPath) where keyPath is a variable


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.


Solution

  • 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)