Keypaths as closures
Swift 4.0 added support for strongly typed key paths. A KeyPath<A, B>
is a type that defines a path from a root type A
, through zero or more hoops, to a resulting type B
through a series of named accessors. It’s a way of accessing a type’s properties indirectly.
It was always possible to store a reference to a function, and call it later, creating a distinction between the function reference and the function invocation. However, for properties this was not possible before Swift 4.
let x = foo.bar // get a reference to the function that you can pass around
x() // call the function
let y = Foo.bar // get a reference to the class function
x == y(foo) // true
y(foo)() // invoke the function on foo
let z = foo.bar() // invoke the function directly
Now, with Swift 4, we can get a similar reference through a KeyPath
, which we can evaluate later:
let x = \Foo.qux // get a key path from a Foo to the property qux
foo[keyPath: x] == foo.qux // true
But while function references can implicitly be used as closures, we cannot do the same with key paths, even though they are both something that takes an A
and produces a B
.
func isEven(_ i: Int) -> Bool {
return i % 2 == 0
}
extension Int {
var isEven: Bool {
return self % 2 == 0
}
}
let numbers = [1, 4, 2, 3, 5, 7, 5, 6]
let even1 = numbers.filter(isEven) // this works: [4, 2, 6]
let even2 = numbers.filter(\Int.isEven) // does not compile!
It is often the case that I want to filter or transform collections of values based on a single property of those values:
let names = people.map(\.name)
let valid = input.filter(\.isValid)
But we can fix this. Enter the ^
prefix operator
prefix operator ^
prefix func ^<A, B> (operand: KeyPath<A, B>) -> (A) -> B {
return { $0[keyPath: operand] }
}
let keyPath = \Person.name // KeyPath<Person, String>
let closure = ^keyPath // (Person) -> String
let names = people.map(\.name) // does not compile
let names = people.map(^\.name) // ["Ben", "Jerry", "Adam"]
Leave a Comment