What is your evaluation of the proposal?
This proposal has the potential to open up vast new design spaces for Swift types and libraries. I am very much in favor of it, and I would be very disappointed to see it closed off to users through something like abuse-mitigation alternative #2.
I would prefer to see this implemented with an attribute instead of a protocol. The proposal already captures and attempts to rebut many of my arguments on this point, so I won't repeat them. However, I would like to respond to this point:
Protocols describe semantics of a conforming type, and this proposal provides key behavior to the type that conforms to it.
While it's true that this protocol marks types which have a particular semantic, the protocol doesn't actually capture any of that semantic's requirements. That means, for instance, that a DynamicMemberLookupProtocol
existential or generic constraint does not give access to the dynamic functionality. I don't like this at all. Pure marker protocols are occasionally a good idea, but not often.
Is the problem being addressed significant enough to warrant a change to Swift?
Yes. We need features like this for dynamic language bridging, but I'm actually more excited about extending this feature for boilerplate reduction.
Suppose that today, you're generating a bunch of code like:
extension Account {
var friends: Resource<[Account]> {
return Resource("account", self, "friends")
}
var blocks: Resource<[Account]> {
return Resource("account", self, "blocks")
}
var posts: Resource<[Post]> {
return Resource("account", self, "posts")
}
}
There might be a whole bunch of different extensions like this on different parts of your model.
I envision a world where you could build a type which functions as a sort of schema of all the properties you want to have:
struct ResourceSchema<Parent, Child> {
var name: String
}
extension ResourceSchema where Parent == Account, Child == [Account] {
static var friends: ResourceSchema { return ResourceSchema(name: "friends") }
static var blocks: ResourceSchema { return ResourceSchema(name: "blocks") }
}
extension ResourceSchema where Parent == Account, Child == [Post] {
static var posts: ResourceSchema { return ResourceSchema(name: "posts") }
}
(In an ideal world, those lines could look like static let friends = ResourceSchema(name: "friends")
instead. Remind me to propose allowing stored static variables on generic types in extensions which fully constrain all the parameters.)
And then you could write a single getter implementation which was used for all of these properties:
struct Account: DynamicMemberLookupProtocol {
...
subscript<Child>(dynamicMember key: ResourceSchema<Account, Child>) -> Resource<Child> {
return Resource("account", self, key.name)
}
}
This could replace a lot of things which currently require code generation without having to go all the way to adding hygienic macros. This is not something that I think the proposal can do today, but I see this as a straightforward extension of the proposed functionality.
Does this proposal fit well with the feel and direction of Swift?
Yes. The use of a subscript is an elegant way to represent member lookup, and the overall feature fits Swift's goal of ensuring code is uncluttered and easy to read. The mechanism is currently limited, but it can be expanded in the future.
I remain unconvinced that a protocol is the right way to represent this feature, but this is ultimately a relatively unimportant detail.
If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?
Most languages I've used with a similar feature work something like Objective-C's -forwardInvocation:
, where all types—whether they have dynamic behavior or not—call some sort of fallback method when a member is not found. Compared to those languages, the proposed mechanism is much better because the compiler can reliably emit errors when a type does not use the fallback lookup feature. For example, Objective-C warns for methods it doesn't know about, but which may eventually be found at runtime; Ruby does not warn about methods it doesn't think exists, so only a method lookup failure at runtime produces an error. The proposed solution is much better than either of these.
How much effort did you put into your review? A glance, a quick reading, or an in-depth study?
I've participated in discussion of this feature at every public stage of its design. I gave the final proposal a quick reading.