[swift-evolution] Pitch: Even Smarter KeyPaths?

Joe Groff jgroff at apple.com
Mon May 15 12:00:25 CDT 2017


> On May 15, 2017, at 8:29 AM, Stephen Celis via swift-evolution <swift-evolution at swift.org> wrote:
> 
> I've started to explore Smart KeyPaths in the latest Swift Development Snapshot with regard to lenses and have been pleasantly surprised: Swift has one of the better out-of-box optics stories I've come across!

Great to hear! Thanks for kicking the tires and filing bugs.

> In (brief) use I've come across a few gaps that I was hoping we could fill in.
> 
> ## Tuple KeyPaths
> 
> I hoped these would work already, but I hit a compiler crash: https://bugs.swift.org/browse/SR-4888
> 
> ```
> struct Location {
>  let coords: (lat: Double, lng: Double)
> }
> 
> \Location.coords.lat
> \Location.coords.0
> ```
> 
> ## Enumeration KeyPaths
> 
> I tried to find discussion around enum KeyPaths but couldn't find any.
> 
> ```
> struct User {
>  let name: String
> }
> 
> enum Result<Success, Failure: Error> {
>  case success(Success)
>  case failure(Failure)
> }
> 
> \Result<User, Error>.success?.name
> ```
> 
> Enumeration cases with multiple values could use tuple-style matching.
> 
> ```
> enum Color {
>  case gray(Double, a: Double)
>  case rgba(r: Double, g: Double, b: Double, a: Double)
>  // ...
> }
> 
> \Color.gray.0?.description
> \Color.rgba.r?.description
> ```

These would both be great incremental additions. The story for enum keypaths seems like it's also a bit tied up with the idea of automatically-derived properties for enums. The idea has come up in the past that an enum's cases should be available as instance properties of optional payload type; that model would also make keypaths for the enum "just work".

> ## Other Ideas
> 
> The above are just quick ideas that would partially cover more use cases of lenses and prisms, but I'd love to explore other optics, as well. E.g, traversals: `\User.friends[..].friends` to return a user's friends' friends.

Subscript key paths aren't implemented in master yet, but when they are, you'd have some ability to tackle this in the library by providing "higher-order" subscript operations on containers, e.g.:

extension Array {
  subscript<EachElement>(each kp: KeyPath<Element, EachElement>) -> [EachElement] {
    return map { $0[keyPath: kp] }
  }
}

\User.friends[each: \.friends]

but it'd be interesting to explore throwing sugar in that direction. This direction also suggests at some point supporting non-symmetric KeyPath types with different types in the "in" and "out" directions, which leads to the prisms, traversals, and other fancier optics you see in lens packages from Haskell and other languages.

-Joe


More information about the swift-evolution mailing list