[swift-evolution] [Draft] Obejctive-C Keypaths

Brent Royal-Gordon brent at architechies.com
Sat Mar 5 00:12:28 CST 2016


> * Foundation is becoming part of the Swift portability story

Foundation is, but KVC is not—Corelibs Foundation does not appear to support it (or even have the stubs for it).

Nevertheless, I do actually think this is a useful feature even without KVC. It is often the case that, in serializing an instance, you need to create fields with names matching the instance's properties. This is true whether you're using NSCoding, RawRepresentable with a dictionary raw value, or something completely custom.

The simplest way to do this is to type a string literal in each place. But that means each name is now duplicated in three places: the property declaration, the writing code, and the reading code. You can reduce that to two by creating constants for the property names, but that has a few issues:

1. There are still two sources of truth (the constants and the properties).
2. It requires a fair bit of discipline and foresight; it's something you have to learn is a good idea, usually through experience.
3. It's kind of a pain.

Supporting something like #keypath() even in environments without Objective-C would allow you to write this kind of code using one source of truth: the property declarations.

(If we did this, the use of `.` for multi-component key paths would be an arbitrary choice on non-Objective-C platforms. But it's as good as any choice. Certain types, like Optional and Array, would have to be marked as "transparent" to the feature; they would be considered equivalent to their generic type.)

If we went this route, I would suggest changing a few things here.

1. #keypath should be equivalent to a string literal, not necessarily a `Swift.String`. That would allow you to store it into StaticString or other StringLiteralConvertible types. (If you had an enum corresponding to the available properties, you might even be able to conform it to StringLiteralConvertible and use this feature with it.)

2. We *may* want to consider calling it something that's slightly less tied to Cocoa terminology, like #name or #property. (#key might be a good compromise between the two.) I don't think this is *necessary*, but we might want to consider it.

3. It would be nice if you could easily get a name for a property on `self`, and it might even make sense to allow you to easily get a name for a property on any variable. Maybe the syntax (assuming that this code is inside a `Person`) would be more like:

	print(#key(Person.bestFriend.firstName))		// => bestFriend.firstName
	print(#key(chris.bestFriend.firstName))			// => bestFriend.firstName
	print(#key(bestFriend.firstName)				// => bestFriend.firstName

Note that that last form has a slightly tricky rule: because the only visible `bestFriend` is a property, `#name(bestFriend.firstName)` is actually `#name(self.bestFriend.firstName)`, so the `self` is what's stripped off. A small redesign would disambiguate:

	print(Person.#key(bestFriend.firstName))		// => bestFriend.firstName
	print(chris.#key(bestFriend.firstName))			// => bestFriend.firstName
	print(#key(bestFriend.firstName)				// => bestFriend.firstName

For this receiver-based form, we might want to repurpose leading dot to refer to the type of the receiver of the method it's being passed to, if it is being passed to a method. In other words, you could write this:

	chris.valueForKeyPath(.#key(firstName))

And it would know to look in `Person` because `chris` is a `Person`. This doesn't really match the conventional meaning of leading dot, but I don't see a way to apply leading dot's usual meaning to this construct anyway.

-- 
Brent Royal-Gordon
Architechies



More information about the swift-evolution mailing list