[swift-evolution] [Pitch #2] Introduce User-defined "Dynamic Member Lookup" Types

Brent Royal-Gordon brent at architechies.com
Wed Nov 29 02:46:42 CST 2017

> On Nov 28, 2017, at 8:35 PM, Chris Lattner <clattner at nondot.org> wrote:
> We’ve had a lot of discussions over the years about how to balance simplicity vs power, implicitness vs explicitness, intentionality vs accidental behavior, etc.  For example, in very early discussions about Swift generics, some folks where strong proponents of protocol conformance being fully implicit: satisfying all the requirements of a protocol meant that you conformed to it, even if you didn’t explicitly “inherit” from it.
> This is obviously not the design we went with over the long term, and I’m glad we didn’t.

I get that, but an attribute in particular would be just as explicit as a protocol conformance.

> I think that DynamicMemberLookup requiring conformance is the same thing: it makes it explicit that the behavior is intentional, and it allows somewhat better error checking (if you conform to the protocol but don’t implement the (implicitly known) requirement, you DO get an error).  That said, this is just my opinion.  

So you envision that the compiler knows that `DynamicMemberLookupProtocol`-conforming types ought to have `subscript(dynamicMember:)` members, and these members ought to have certain traits (unary, ExpressibleByStringLiteral parameter, etc.), and it should enforce those traits, even though there's no matching requirement in the protocol?

That seems like a lot of compiler magic to attach to a particular protocol. A `@dynamicMember` attribute would have a similar amount of magic, but people *expect* that kind of magic from an attribute. For instance, the `@objc` attribute places lots of conditions on the types of parameters, etc., and nobody is surprised by that.

Other reasons to prefer an attribute:

* I can't think of a use case where you would want dynamic members but not want to make the subscript itself accessible. If `@dynamicMember` could be applied to any subscript, then you could use any label (or no label) for sugar-free use of the dynamic functionality. For instance, the JSON example could decorate its existing subscript instead of introducing a redundant one:

	extension JSON {
	  var stringValue : String? {
	    if case .StringValue(let str) = self {
	      return str
	    return nil
	  subscript(index: Int) -> JSON? {
	    if case .ArrayValue(let arr) = self {
	      return index < arr.count ? arr[index] : nil
	    return nil
	  @dynamicMember subscript(key: String) -> JSON? {
	    if case .DictionaryValue(let dict) = self {
	      return dict[key]
	    return nil

* If we eventually want to support multiple subscripts with different types (either by using different return types, or by later supporting non-`ExpressibleByStringLiteral` fixed attribute sets), allowing multiple subscripts to be annotated by `@dynamicMember` is more natural than allowing multiple `subscript(dynamicMember:)` members to simultaneously satisfy a single protocol pseudo-requirement.

The only thing you're using the `DynamicMemberLookupProtocol` for is to mark the type for the compiler to recognize; you're not using any of the other capabilities of protocols. That would be okay if you were proposing something that wouldn't touch the compiler if implemented with a protocol, but this feature definitely requires significant compiler work. It'd be okay if all you needed to do was mark a type, but this feature also requires you to implement certain members, following certain conventions, which happen to be difficult to express through protocols. It'd be okay if a type needed many members to meet the requirements, but it only needs one. It'd be okay if you needed to constrain generic calls to require conformance to the type, but there's no need for that here.

Basically, you're using a hammer to drive a screw. Why not use a screwdriver instead?

Brent Royal-Gordon

More information about the swift-evolution mailing list