Pitch: Even Smarter KeyPaths?

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!

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

## 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.

···

-
Stephen

5 Likes

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

···

On May 15, 2017, at 8:29 AM, Stephen Celis via swift-evolution <swift-evolution@swift.org> wrote:

I’d love to reopen the idea of tuple key paths. I don’t think they’d carry the same baggage as enum key paths, since a tuple key path wouldn’t require any new syntax or features like auto-derived properties. I was surprised just now when I tried creating a key path on a tuple and found that it didn’t work. It’s especially weird because this contrived code sample compiles:

func doSomething(with keyPath: WritableKeyPath<(int: Int, string: String), String>) {
    var tuple = (int: 2, string: "hi")
    tuple[keyPath: keyPath] = "new value"
}

But you can never actually call that function, because you can’t construct a key path that satisfies the type of the parameter:

doSomething(with: \.string) // Type '(int: Int, string: String)' has no member 'string'

Currently using Xcode 9.2.

Tuple keypath components would be a very easy thing to add. They might even make a good “starter bug” for someone interested in learning a bit about how key paths are implemented in the compiler and runtime.

2 Likes