Swift 5.2: Keypaths Get a Promotion

When you’re busy programming, it’s easy to sleep through changes in the Swift language, especially minor changes; and Swift 5.2 (which emerged as part of Xcode 11.4) was definitely minor, especially in comparison with Swift 5.1. Still, this is an interesting little improvement worth knowing about.

Suppose that a Person has a firstName and a lastName, both String properties. And suppose you write this:

let names = arrayOfPersons.map {$0.firstName}

New in Swift 5.2, you can write this instead:

let names = arrayOfPersons.map (\.firstName)

One is tempted to think: Oh, this is just another way of using map — and filter, and anything else where you might use a one-line anonymous function of that form. That’s quite nice; there are places where you have to jump through extra hoops to use an anonymous function (for instance, when curly braces follow), and you can avoid that by using the second notation. But it feels like merely a bit of syntactic sugar.

But let’s go further. Let’s concentrate on why that works: the expression \.firstName behaves as if were the name of a function. What kind of function? Well, here it’s effectively the name of a function that takes a Person and returns a String. That means we can use it in any context that expects a function that takes a Person and returns a String.

To demonstrate, I’ll define Person to have a method that takes a function of that form:

struct Person {
    let firstName : String
    let lastName : String
    func stringMaker(_ f: (Person)->String) -> String {f(self)}
}

We are now allowed to hand to a Person’s stringMaker method any function that takes a Person and returns a String. Here’s a trivial example:

let p = Person(firstName: "Matt", lastName: "Neuburg")
func sayHello(from p:Person) -> String {
    "Hello from " + p.firstName
}
print(p.stringMaker(sayHello)) // Hello from Matt

Or of course I could write it using “trailing closure” syntax:

let s = p.stringMaker {
    "Hello from " + $0.firstName
}
print(s) // Hello from Matt

The point, however, is that a keypath like \.firstName now happily goes in that slot:

print(p.stringMaker(\.firstName)) // Matt

And this is not just for parameter passing. It’s for all situations where a function of this type is expected. Here’s an example using direct assignment to a variable:

let f : (Person) -> String = \Person.firstName
f(p)

Yes, that is legal! Note that it would not be legal without the explicit typing of f. You can’t write this:

let f = \Person.firstName
f(p) // error

That’s because f is inferred to be a KeyPath, not a function. A keypath is not of itself a function; rather, you can use it where a function is expected, evidently because the keypath is promoted to be a function. In fact, the name of one of the threads on the Swift forum proposing this innovation was “Key path getter promotion.”

(The current proposal also recommends that you be allowed to perform that promotion explicitly by casting, but I do not find that that is the case.)

Another limitation is that this promotion works only with keypath literals. This is illegal:

let path = \Person.firstName
let f : (Person) -> String = path // error

So, in thinking about the significance of this innovation for my own work, I’m going to try to think that way. In any situation that expects a function that takes an instance of type A and produces an instance of type B, if type A has a property of type B, the literal keypath pointing to that property is such a function.

You Might Also Like…

Updating Your App to iOS 14

Each year, like clockwork, for better or worse — sometimes, in my opinion, very much for worse — Apple releases a new version of iOS. We’re all used to the forced march of the calendar by now. In June, WWDC is held, and the new version appears in beta. In September, it goes final. And …

Updating Your App to iOS 14 Read More »

    Sign Up

    Get more articles and exclusive content that takes your iOS skills to the next level.