# Key Path Expressions as Functions

#21

I have found a use for `ExpressibleByKeyPathLiteral`. It is a little complicated to explain, but I’ll try to summarize.

Let’s say we have some data, and we want to sort it first by one property, then another, and so forth. We could write a variadic `sort` function to do so, and at the call-side it might look like this:

``````var pets: [Animal] = ...

pets.sort(\.age, \.weight, \.name)
``````

But we can’t make that work today since we don’t have variadic generics.

What we can do, however, is this:

``````pets.sort(.by(\.age), .by(\.weight), .by(\.name))
``````

I have written a proof-of-concept implementation, and it works. Indeed, it works generically for any type, and variadically for any number of keypaths, and it works in Swift today even without variadic generics.

However, it would be nice to be able to just write the keypath literal, rather than having to wrap it in `.by()`. Thus, I have a non-trivial use-case for `ExpressibleByKeyPathLiteral`.

(John McCall) #22

Ah, interesting. So that's still just sugaring the wrapping of a `KeyPath`, but it does make it clearer why that might be useful.

#23

Yes. Moreover, even if we eventually do gain variadic generics, it will still be useful.

If we just had variadic generics then you could use all keypaths in the `sort` call, but there would be no way to specify which ones should be put in descending order.

On the other hand, my example implementation allows you to specify the sorting predicate, eg. `.by(\.age, >)`. Thus with `ExpressibleByKeyPathLiteral` you could mix-and-match using `.by()` when you need to change the sort order, and keypath literals for the rest.

I just realized that this proposal actually yells for re-evaluation of the inclusion of `isFalse` to `Bool`, because a negated value of a boolean is not accessible right now.

``````users.filter(\.isAdmin.isFalse)
``````

(Maksim Odnoletkov) #25

Or, more general, for addition of the function composition operator to enable

``````users.filter(not • \.isAdmin)
``````

(Xiaodi Wu) #26

Why wouldn't we simply create an appropriate overload of `prefix !` to work as expected?

``````prefix func ! <Root>(keyPath: KeyPath<Root, Bool>) -> (Root) -> Bool {
return { root in !root[keyPath: keyPath] }
}

[0.0, 1.0, 2.0].filter(!\.isZero)
``````

#27

Wouldn't you want the overload to work with `(Root) -> Bool` instead?

``````prefix func ! <A>(predicate: (A) -> Bool) -> (A) -> Bool {
return { !predicate(\$0) }
}

[0.0, 1.0, 2.0].filter(!\.isZero)
``````

And then you get into the question of which overloads you want on functions in general. Why not `&&`, `||`, etc.?

(Xiaodi Wu) #28

Sure; my example works in Swift today for key paths, whereas yours would require the feature pitched here.

#29

Maybe I just don't understand your suggestion. Are you saying that adding a `!` overload to `KeyPath` is preferable to a more general solution on functions?

Obviously that would also work and I have thought about it as well, but I prefer a more readable solution over the visually terse `(!\.`. Also it‘s not the key-path that you are negating, but the extracted value in the closure, while it reads the other way around.

Furthermore you could use `isFalse` in not function related key-paths (`!` is there too, but it‘s similar to `toggle` visually far apart and hard to spot.)

``````// with the operator
view.isHidden = !viewController[keyPath: \.view.isEnabled]

// using `isFalse`
view.isHidden = viewController[keyPath: \.view.isEnabled.isFalse]
``````

My preference would be the direct type member over the operator for clarity and because it‘s followed direct after the boolean itself. You could have a more complex keypath expression where the boolean value you want to extract is nested further down the chain. The distance for `isFalse` would still remain one while the operator would distance itself from the boolean with each additional step the chain requires and loosen up both readability and clarity IMO.

Feel free to correct me, but I tend to say that in Swift we wanted to avoid overloading operators as much as possible, which would also speak against the extra overload here.

(Jean-Daniel) #31

(Gwendal Roué) #32

It never stops. What about force-unwrapping optionals?

``````players.map(\.email!) // [String]
``````

Maybe, maybe, we should remember that this pitch, as insanely lovely as it is, is not a silver bullet, and that closures are still our friends:

``````players.map { \$0.email! }
[0.0, 1.0, 2.0].filter { !\$0.isZero }
``````

After all, that's what people do in Ruby:

``````values.select(&:is_foo)         # sugar
values.select { |v| !v.is_foo } # no sugar
``````

#33

``````\[Int].first  // KeyPath<[Int], Int?>
\[Int].first! // KeyPath<[Int], Int>
``````

Alternately:

``````players.map(\.email!)
[0.0, 1.0, 2.0].filter(! <<< \.isZero)
``````

#34

Wouldn't it be nice if:

1. Enums had first-class support for key paths.
2. Bool were simply `enum Bool { case true, false }`

That's all you'd need for `\.view.isEnabled.isFalse` to be derived automatically ;)

That is something I also want to answer the question regarding `Optional`, `[nil, 42].count(where: \.isNone)`. However I'm not sure if we can swap the representation of a `Bool` from being a `struct` to an `enum`. If we could, then sure I'd support that change.

(Gwendal Roué) #36

Wow, I didn't know about the force-unwrapping of keyPaths. That's pretty cool.

I just meant that those extensions may belong to user code, not to the standard library. The pitch is already good without this sugar.

To be clear, I wasn't saying that something like `isFalse` should be included into this proposal. Instead I think it's a reasonable thing to consider after this proposal was accepted, especially because operations with a predicate would miss the opposite state when used key-pathes as a simple machinery for reading values. Other than that, this proposal is just perfect.

(Happy Human Pointer) #38

I also do not like is `isFalse`. It leads to ambiguities like this:

``````struct Email {
let isFalse: Bool
}
struct User {
let email: Email
}
let user: User? = ...
user.flatMap(\.email.isFalse)
``````

However, I think the proposal should be a welcome change. Anyone looking to be the implementer?

Does this invert `(User) -> Email` or is it accessing `isFalse`? I chose a bad example here, but there could be cases in which `isFalse` could eater mean a property or the key-path-inversion syntax. I am for `!`. Sorry for the confusion.