Some small keypath extensions: identity and tuple components

There two small gaps in key paths that'd be nice to fill in:

  • There's no way to form an identity key path today, short of writing a property definition. The identity keypath is useful to be able to pass to APIs that take a key path to describe what part of a value to manipulate in order to say "all of it", among other things. We have the magic .self member in every type, and we could conceivably spell the identity key path that way, as \.self. The identity keypath would be a WritableKeyPath<T, T>, and appending an identity key path would be an identity operation, so that (\.self).appending(x) == x and x.appending(\.self) == x.
  • Tuple elements should be allowed in key paths, either labeled or indexed, and form WritableKeyPath<(..., T, ...), T> values.

Of course, there are infinite other features that could be considered, but I'd like to focus on these two because they're small incremental features that fit easily into the current model. The identity key path also would need a small amount of runtime support to get the correct appending behavior that would be good to get into the runtime before it stabilizes. How does this sound?

31 Likes

+1 on both concepts, and the second one seems like obvious goodness. For the first one, I hope we can find a more clear way of expressing this. I get what you're going for with .self, but that is an ObjC thing, not a Swift thing.

\.self sounds clear enough to me: it's very similar to the self property available on types and both can be chained:

\.self.self.self.self == \.self
self.self.self.self == self

But keypaths apply to instances, right? Objective-C has a self member for instances, but Swift does not.

print(1.self) works fine. Maybe that’s a keyword rather than a real property, but that’s real enough for me.

Currently, it's allowed to declare

var `self`: T

Aside from the question wether that is a good idea or not, I'd prefer a solution that simply can't interfere with real properties.
Forbidding "self" as a member name would be one way around that issue, the other would be a string that can never be the name of a property.
\. is probably the most obvious one (and if Swift would use slashes instead of points for path separation, Unix guys would feel home right away ;-)

The quoted identifier `self` is different from the keyword self, though. I agree that \.self is not the ideal syntax, especially because with an explicit root type it looks like \T.self, which is only one character away from a type value reference T.self. Ideally, if we ever get around to revisiting SE-90, we would be able to kill the .self member entirely. \. is cute but maybe a little mysterious. What about a static member of AnyKeyPath?

extension AnyKeyPath {
  static func identity<T>() -> WritableKeyPath<T, T>
}

This would let you write .identity() in context where a KeyPath or any subclass thereof is expected.

In a current discussion about single element tuples there was mention of this thread, and a suggestion to use \.0 as the identity keypath element, here.
This would effectively be coercing T to an unlabeled single element tuple (T) getting it's first element, and coercing back to T, except unlabeled single element tuples don't exist.
And even labeled single element tuples only exist in some parts of the compiler.

2 Likes

Personally, I think \. is short, sweet, and, erhm, to the point! More to the point, perhaps, it seems easy to come up with: it's the first thing I'd think of for how to spell the identity keypath, and I could remember it rather than having to google for how to spell it otherwise. When you first suggested this concept my first thought was "hmm, too bad it can't be spelled \. because that would be so obvious."

3 Likes

FWIW, \. was the first thing I have tried some time ago when I wanted the identity path and was wondering if that’s supported.

Agree, but it's worth considering. It is somewhat reminiscent of using . to refer to the current working directory. That analogy might be enough to help people remember what it means.

3 Likes

One theoretical downside to \., besides the cryptic nature, is it makes more partially-written code valid. Probably not a big deal in practice though, especially since keypaths are a somewhat niche feature at the moment.

4 Likes

I also like "." for the identity key path.

Since appending the identity key path is an identity operation, it seems this would be valid syntax:

let keyPath = \Person.....address......city......

It might be worth considering how the compiler treats expressions like that.

Can we add support for static members into the bucket of small extensions?

struct MyType {
  static var staticMember = "swift"
}

\MyType.Type.staticMember

That would require some more infrastructure to support. I would prefer to focus on these two, as I noted in the original post.

3 Likes

No objection, but I‘d wish to see it in Swift 5 though. :slight_smile:

FWIW, I ran into a use case yesterday where the identity key path is exactly what I need. I can work around not having it, but it would be much better if I didn't have to. :slight_smile:

About small improvements to key paths, it would be nice to know if a key path is a property (like \Value.field) or a sub-property (like \Value.field.subField). And to be able to decompose a key path if it is a sub-property.

I needed that once, when I handled lazy-loading with key paths. To lazily load the sub-property of a value, you have to lazily load the property then the sub-property. So you need the decomposition of the key path.

I prefer a bare \ for the identity KeyPath. My rationale:

  • It feels like root, akin to the / directory
  • Unlike \., it obeys the rule that, textually, you append one KeyPath to another by adding a dot and the attribute name, e.g., the name of \.father is \.father.name. Accordingly, if you iteratively stripped away attributes in that KeyPath, you'd be left with \.father.name\.father\ .
3 Likes

Key path decomposition would be another thing that's straightforward to add to the current implementation, but I'd appreciate if we could discuss it as a separate topic.

1 Like