Skip to content

Instantly share code, notes, and snippets.

@erica erica/keypath.md
Last active Jan 16, 2019

Embed
What would you like to do?

Keypath Literal Getter Promotion

  • Proposal: SE-nnnn
  • Authors: TBD
  • Review Manager: TBD
  • Status: Awaiting implementation

Introduction

This proposal introduces a compiler-supported feature to promote keypaths to getter functions wherever appropriate.

Discussions

Motivation

One-off closures that traverse from a root type to a value are common in Swift. Consider the following User struct:

struct User {
    let email: String
    let isAdmin: Bool
}

Applying map allows the following code to gather an array of emails from a source user array:

users.map({ $0.email })

Similarly, filter can collect an array of admins:

users.filter({ $0.isAdmin })

These ad hoc closures are short and sweet but Swift already generates this code in the form of key paths. The Swift forum has previously proposed adding map, flatMap, and compactMap overloads that accept key paths as input. Popular libraries also add key path overloads.

Adding an overload per function is a losing battle. It's better to introduce first-class support from the language by promoting keypaths to functions. Swift should accept KeyPath<Root, Value> keypaths wherever it allows (Root) -> Value functions:

users.map(\.email)

users.filter(\.isAdmin)

These features exist in Ruby, which allows Symbol#to_proc to express similar traversals:

users.map(&:email)

users.filter(&:isAdmin)

The ^ prefix operator

The ^ prefix operator offers a common third party solution for many users:

prefix operator ^

prefix func ^ <Root, Value>(keyPath: KeyPath<Root, Value>) -> (Root) -> Value {
  return { root in root[keyPath: keyPath] }
}

Although handy, it is less readable and less convenient than compiler-sourced support for direct keypath use.

Detailed design

This design extends the compiler such that \.foo can be used in places that expect functions of type (Root) -> Value , and is simply shorthand for { $0[keyPath: \.foo] }. It is the least disruptive approach that supports keypath syntax as a function type.

PR 19448

This would not:

  • Be a way to define any other type as callable.
  • Be a way to create any other type from a key path literal (e.g. This is not an ExpressibleByKeyPathLiteral).
  • Be a coercion between KeyPath and function type (e.g. you can't convert between with as or anything like that.)

Note: See Joe's feedback on the PR

Source compatibility

This is additive.

Effect on ABI stability

N/A

Effect on API resilience

N/A

Alternatives considered

  • Adding the ^ prefix operator

Future steps

This proposal moves Swift forward without going all the way to an Expressible... protocol. Closures, as non-nominal types, couldn't conform to that protocol anyway and they are the primary driver of this change. Were Swift to adopt this proposal, it could later address protocols, along with adding ArrayLiteralType and DictionaryLiteralType.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.