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

Hi all,

I’ve significantly revised the ‘dynamic member lookup’ pitch, here’s the second edition:
https://gist.github.com/lattner/b016e1cf86c43732c8d82f90e5ae5438

I’ve incorporated some minor changes to it:
- I’ve made it possible to provide read-only dynamic members.
- I’ve added an example JSON use-case which uses read-only dynamic members.
- Minor wording changes.

That said, this is significantly similar to the earlier draft. I welcome suggestions for improvements to the proposal, and insight into anything that is unclear or insufficiently motivated.

Thanks!

-Chris

I’m very happy with this new version of the proposal. Splitting into two protocols makes a lot of sense - the JSON example alone is argument enough.

To bike-shedding: while I understand the need to keep the identifier names verbose, I would prefer the subscript label to be more representative by renaming it to dynamicMember - lookup can be interpreted as a verb, which is odd in a label.

···

On 21 Nov 2017, at 07:36, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:

Hi all,

I’ve significantly revised the ‘dynamic member lookup’ pitch, here’s the second edition:
https://gist.github.com/lattner/b016e1cf86c43732c8d82f90e5ae5438

I’ve incorporated some minor changes to it:
- I’ve made it possible to provide read-only dynamic members.
- I’ve added an example JSON use-case which uses read-only dynamic members.
- Minor wording changes.

That said, this is significantly similar to the earlier draft. I welcome suggestions for improvements to the proposal, and insight into anything that is unclear or insufficiently motivated.

Thanks!

-Chris

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Just to talk to myself a bit here, but I’ve come to realize that the right design really is to have a simple empty marker protocol like this:

/// Types type conform to this protocol have the behavior that member lookup -
/// accessing `someval.member` will always succeed. Failures to find normally
/// declared members of `member` will be turned into subscript references using
/// the `someval[dynamicMember: member]` member.
///
public protocol DynamicMemberLookupProtocol {
  // Implementations of this protocol must have a subscript(dynamicMember:)
  // implementation where the keyword type is some type that is
  // ExpressibleByStringLiteral. It can be get-only or get/set which defines
  // the mutability of the resultant dynamic properties.
  
  // subscript<KeywordType: ExpressibleByStringLiteral, LookupValue>
  // (dynamicMember name: KeywordType) -> LookupValue { get }
}

A design like this can almost work:

public protocol DynamicMemberLookupProtocol {
  associatedtype DynamicMemberLookupKeyword : ExpressibleByStringLiteral
  associatedtype DynamicMemberLookupValue
  
  subscript(dynamicMember name: DynamicMemberLookupKeyword)
    -> DynamicMemberLookupValue { get }
}

The problem is that now everything that conforms to DynamicMemberLookupProtocol is a PAT, so it doesn’t work with existentials. We could almost make due with a generic subscript:

public protocol DynamicMemberLookupProtocol {
  subscript<KeywordType: ExpressibleByStringLiteral, LookupValue>
    (dynamicMember name: KeywordType) -> LookupValue { get }
}

but it turns out that while you can declare that, nothing can actually conform to it with concrete types (I filed SR-6473, but it isn’t clear that it ever can work given how our generics system works).

Defining this as an empty marker protocol has several advantages:
- Only one protocol is required
- Suddenly you can define mutating getters and nonmutating setters
- Existentials work as well as concrete types.

-Chris

···

On Nov 20, 2017, at 10:36 PM, Chris Lattner <clattner@nondot.org> wrote:

Hi all,

I’ve significantly revised the ‘dynamic member lookup’ pitch, here’s the second edition:
https://gist.github.com/lattner/b016e1cf86c43732c8d82f90e5ae5438

I’ve incorporated some minor changes to it:
- I’ve made it possible to provide read-only dynamic members.
- I’ve added an example JSON use-case which uses read-only dynamic members.
- Minor wording changes.

I’m very happy with this new version of the proposal. Splitting into two protocols makes a lot of sense - the JSON example alone is argument enough.

Cool.

To bike-shedding: while I understand the need to keep the identifier names verbose, I would prefer the subscript label to be more representative by renaming it to dynamicMember - lookup can be interpreted as a verb, which is odd in a label.

Makes sense, I re-removed “Lookup”.

I also just added a new alternative: "Collapse the two protocols into a single one with a get-only requirement”. It is either crazy bad or crazy good, let me know what you think.

-Chris

···

On Nov 21, 2017, at 12:04 AM, David Hart <david@hartbit.com> wrote:

If you're reaching this point. why have a marker protocol at all? Why not treat `subscript(dynamicMember:)` specially on any type that has it, or have an `@dynamicMember subscript(_:)` attribute, or introduce an entire new `dynamicMember(_:)` declaration?

···

On Nov 25, 2017, at 3:16 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:

Just to talk to myself a bit here, but I’ve come to realize that the right design really is to have a simple empty marker protocol like this:

--
Brent Royal-Gordon
Architechies

I noticed all of the examples return the same type as they are defined on (JSON has a subscript that returns JSON). Is there an example of where this is not the case?

···

On Nov 25, 2017, at 3:16 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:

On Nov 20, 2017, at 10:36 PM, Chris Lattner <clattner@nondot.org <mailto:clattner@nondot.org>> wrote:

Hi all,

I’ve significantly revised the ‘dynamic member lookup’ pitch, here’s the second edition:
https://gist.github.com/lattner/b016e1cf86c43732c8d82f90e5ae5438

I’ve incorporated some minor changes to it:
- I’ve made it possible to provide read-only dynamic members.
- I’ve added an example JSON use-case which uses read-only dynamic members.
- Minor wording changes.

Just to talk to myself a bit here, but I’ve come to realize that the right design really is to have a simple empty marker protocol like this:

/// Types type conform to this protocol have the behavior that member lookup -
/// accessing `someval.member` will always succeed. Failures to find normally
/// declared members of `member` will be turned into subscript references using
/// the `someval[dynamicMember: member]` member.
///
public protocol DynamicMemberLookupProtocol {
  // Implementations of this protocol must have a subscript(dynamicMember:)
  // implementation where the keyword type is some type that is
  // ExpressibleByStringLiteral. It can be get-only or get/set which defines
  // the mutability of the resultant dynamic properties.
  
  // subscript<KeywordType: ExpressibleByStringLiteral, LookupValue>
  // (dynamicMember name: KeywordType) -> LookupValue { get }
}

A design like this can almost work:

public protocol DynamicMemberLookupProtocol {
  associatedtype DynamicMemberLookupKeyword : ExpressibleByStringLiteral
  associatedtype DynamicMemberLookupValue
  
  subscript(dynamicMember name: DynamicMemberLookupKeyword)
    -> DynamicMemberLookupValue { get }
}

The problem is that now everything that conforms to DynamicMemberLookupProtocol is a PAT, so it doesn’t work with existentials. We could almost make due with a generic subscript:

public protocol DynamicMemberLookupProtocol {
  subscript<KeywordType: ExpressibleByStringLiteral, LookupValue>
    (dynamicMember name: KeywordType) -> LookupValue { get }
}

but it turns out that while you can declare that, nothing can actually conform to it with concrete types (I filed SR-6473, but it isn’t clear that it ever can work given how our generics system works).

Defining this as an empty marker protocol has several advantages:
- Only one protocol is required
- Suddenly you can define mutating getters and nonmutating setters
- Existentials work as well as concrete types.

-Chris

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Better yet, since we previously started to require “@objc dynamic” instead
of “dynamic” with the notion that perhaps there would be some future
non-ObjC dynamism, we *could* have it spelt “dynamic member(_:)”.

But this is all just spelling now; I think the overall design is compelling
in its elegance and power.

···

On Mon, Nov 27, 2017 at 20:22 Brent Royal-Gordon via swift-evolution < swift-evolution@swift.org> wrote:

On Nov 25, 2017, at 3:16 PM, Chris Lattner via swift-evolution < > swift-evolution@swift.org> wrote:

Just to talk to myself a bit here, but I’ve come to realize that the right
design really is to have a simple empty marker protocol like this:

If you're reaching this point. why have a marker protocol at all? Why not
treat `subscript(dynamicMember:)` specially on any type that has it, or
have an `@dynamicMember subscript(_:)` attribute, or introduce an entire
new `dynamicMember(_:)` declaration?

--
Brent Royal-Gordon
Architechies

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

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. That said, if we did, then all of the “ExpressibleBy” protocols wouldn’t need to exist: we’d probably just say that it was enough to implement the requirements to get the behavior and elide the protocol declaration entirely.

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.

Do you feel strongly enough about this that you’d like to make a strong argument for changing the behavior?

-Chris

···

On Nov 27, 2017, at 6:21 PM, Brent Royal-Gordon <brent@architechies.com> wrote:

On Nov 25, 2017, at 3:16 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Just to talk to myself a bit here, but I’ve come to realize that the right design really is to have a simple empty marker protocol like this:

If you're reaching this point. why have a marker protocol at all? Why not treat `subscript(dynamicMember:)` specially on any type that has it, or have an `@dynamicMember subscript(_:)` attribute, or introduce an entire new `dynamicMember(_:)` declaration?

I noticed all of the examples return the same type as they are defined on (JSON has a subscript that returns JSON). Is there an example of where this is not the case?

Yes, they exist, there is one in the Python interop layer that I’m working on: it is a separate type that vends all the Python builtins. It returns them as PyVal's.

-Chris

···

On Nov 29, 2017, at 2:21 AM, Jonathan Hull <jhull@gbis.com> wrote:

On Nov 25, 2017, at 3:16 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Nov 20, 2017, at 10:36 PM, Chris Lattner <clattner@nondot.org <mailto:clattner@nondot.org>> wrote:

Hi all,

I’ve significantly revised the ‘dynamic member lookup’ pitch, here’s the second edition:
https://gist.github.com/lattner/b016e1cf86c43732c8d82f90e5ae5438

I’ve incorporated some minor changes to it:
- I’ve made it possible to provide read-only dynamic members.
- I’ve added an example JSON use-case which uses read-only dynamic members.
- Minor wording changes.

Just to talk to myself a bit here, but I’ve come to realize that the right design really is to have a simple empty marker protocol like this:

/// Types type conform to this protocol have the behavior that member lookup -
/// accessing `someval.member` will always succeed. Failures to find normally
/// declared members of `member` will be turned into subscript references using
/// the `someval[dynamicMember: member]` member.
///
public protocol DynamicMemberLookupProtocol {
  // Implementations of this protocol must have a subscript(dynamicMember:)
  // implementation where the keyword type is some type that is
  // ExpressibleByStringLiteral. It can be get-only or get/set which defines
  // the mutability of the resultant dynamic properties.
  
  // subscript<KeywordType: ExpressibleByStringLiteral, LookupValue>
  // (dynamicMember name: KeywordType) -> LookupValue { get }
}

A design like this can almost work:

public protocol DynamicMemberLookupProtocol {
  associatedtype DynamicMemberLookupKeyword : ExpressibleByStringLiteral
  associatedtype DynamicMemberLookupValue
  
  subscript(dynamicMember name: DynamicMemberLookupKeyword)
    -> DynamicMemberLookupValue { get }
}

The problem is that now everything that conforms to DynamicMemberLookupProtocol is a PAT, so it doesn’t work with existentials. We could almost make due with a generic subscript:

public protocol DynamicMemberLookupProtocol {
  subscript<KeywordType: ExpressibleByStringLiteral, LookupValue>
    (dynamicMember name: KeywordType) -> LookupValue { get }
}

but it turns out that while you can declare that, nothing can actually conform to it with concrete types (I filed SR-6473, but it isn’t clear that it ever can work given how our generics system works).

Defining this as an empty marker protocol has several advantages:
- Only one protocol is required
- Suddenly you can define mutating getters and nonmutating setters
- Existentials work as well as concrete types.

-Chris

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

Better yet, since we previously started to require “@objc dynamic” instead of “dynamic” with the notion that perhaps there would be some future non-ObjC dynamism, we *could* have it spelt “dynamic member(_:)”.

I’m super open the changing the spelling of anything in the proposal. That said, if you really mean to suggest that we introduce some new syntactic form, I’d be wary of that. It increases the scope of the proposal and makes the cost/benefit tradeoff harder to justify. One of the things that I think is compelling about this is that the cost of it is very low (it really does fit naturally into the existing architecture of the compiler), which means that the narrow benefit is justifiable (IMO).

But this is all just spelling now; I think the overall design is compelling in its elegance and power.

I’m glad to hear that, thanks!

-Chris

···

On Nov 27, 2017, at 7:31 PM, Xiaodi Wu <xiaodi.wu@gmail.com> 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?

···

On Nov 28, 2017, at 8:35 PM, Chris Lattner <clattner@nondot.org> wrote:

--
Brent Royal-Gordon
Architechies

Hi Chis,

Thank you for pushing this forward.

My only comment is that on the declaration side it would be great to also have an attribute to communicate that compiler magic is happening.

Currently it is surprising that a regular looking protocol is providing me so much power.

Suggestions:

@dynamic
struct PyVal : MemberLookupProtocol {...}

@dynamic
struct ParameterSummer : DynamicCallable {...}

// Error: This type needs the @dynamic attribute.
class ParamWinter : MyCustomCallableProtocolOrClassOrTypeAlias {...}

By requiring @dynamic (Or other attribute name), people can know that this is a compiler dynamic declaration and not just some random protocol whose name starts with Dynamic*. :slight_smile:

@NSManagedObject is another example I like from Core Data.
https://useyourloaf.com/blog/core-data-code-generation/

- Cheyo

···

On Nov 28, 2017, at 8:35 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:

On Nov 27, 2017, at 6:21 PM, Brent Royal-Gordon <brent@architechies.com <mailto:brent@architechies.com>> wrote:

On Nov 25, 2017, at 3:16 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Just to talk to myself a bit here, but I’ve come to realize that the right design really is to have a simple empty marker protocol like this:

If you're reaching this point. why have a marker protocol at all? Why not treat `subscript(dynamicMember:)` specially on any type that has it, or have an `@dynamicMember subscript(_:)` attribute, or introduce an entire new `dynamicMember(_:)` declaration?

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. That said, if we did, then all of the “ExpressibleBy” protocols wouldn’t need to exist: we’d probably just say that it was enough to implement the requirements to get the behavior and elide the protocol declaration entirely.

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.

Do you feel strongly enough about this that you’d like to make a strong argument for changing the behavior?

-Chris

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

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.

Sure, I’m not attached to spelling. I think that conformance is the right way to do this (following the pattern of the other ExpressibleBy etc types), but if you have a compelling argument for some specific spelling, I’m more than happy to discuss it.

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?

Yes, this is implemented in the patch, it is a straight-forward additional check in protocol conformance.

Keep in mind that the compiler has other similar things for features that are not expressible in the Swift type system, e.g. the type(of:) function.

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.

I don’t really think that’s the case. Attributes have their place, for sure, but there in high precedent for Protocols providing exposing language features to types.

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:

I see the small advantage, but isn’t precedented at all. The ExpressibleBy protocols require redeclaring redundant methods just like this.

* If we eventually want to support multiple subscripts with different types (either by using different return types,

This is already supported in the patch, and the patch includes a test case.

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

I don’t see a connection between this rhetorical mechanic and the topic at hand :slight_smile:

-Chris

···

On Nov 29, 2017, at 12:46 AM, Brent Royal-Gordon <brent@architechies.com> wrote:

On Nov 28, 2017, at 8:35 PM, Chris Lattner <clattner@nondot.org> wrote:

Hi Chis,

Thank you for pushing this forward.

My only comment is that on the declaration side it would be great to also have an attribute to communicate that compiler magic is happening.

Currently it is surprising that a regular looking protocol is providing me so much power.

Suggestions:

@dynamic
struct PyVal : MemberLookupProtocol {...}

@dynamic
struct ParameterSummer : DynamicCallable {...}

// Error: This type needs the @dynamic attribute.
class ParamWinter : MyCustomCallableProtocolOrClassOrTypeAlias {...}

By requiring @dynamic (Or other attribute name), people can know that this is a compiler dynamic declaration and not just some random protocol whose name starts with Dynamic*. :slight_smile:

I’m not fond of the idea of an attribute. This introduce redundancy. What a declaration means if the attribute is missing ? What this attribute will mean on an other declaration ?
If this attribute must be used with the declaration and it can’t be used with an other one, then what is the point of having an attribute but to exercice the compiler fixit feature

@NSManagedObject is another example I like from Core Data.
https://useyourloaf.com/blog/core-data-code-generation/

@NSManageObject apply to normal declarations that have a different meaning when this attribute is not present.

···

Le 3 déc. 2017 à 04:58, Jose Cheyo Jimenez via swift-evolution <swift-evolution@swift.org> a écrit :

I’m not a huge fan of this (because it is redundant as Jean-Daniel mentions downthread), but it is definitely possible. I’ll add it to the alternatives section so that people consider it during review.

-Chris

···

On Dec 2, 2017, at 7:58 PM, Jose Cheyo Jimenez <cheyo@masters3d.com> wrote:

Hi Chis,

Thank you for pushing this forward.

My only comment is that on the declaration side it would be great to also have an attribute to communicate that compiler magic is happening.

Currently it is surprising that a regular looking protocol is providing me so much power.

Suggestions:

@dynamic
struct PyVal : MemberLookupProtocol {...}

@dynamic
struct ParameterSummer : DynamicCallable {...}

// Error: This type needs the @dynamic attribute.
class ParamWinter : MyCustomCallableProtocolOrClassOrTypeAlias {...}

By requiring @dynamic (Or other attribute name), people can know that this is a compiler dynamic declaration and not just some random protocol whose name starts with Dynamic*. :slight_smile:

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.

Sure, I’m not attached to spelling. I think that conformance is the right way to do this (following the pattern of the other ExpressibleBy etc types), but if you have a compelling argument for some specific spelling, I’m more than happy to discuss it.

I completely agree that protocol conformance is the right away to do it. One similar approach is Scala’s Dynamic <https://blog.scalac.io/2015/05/21/dynamic-member-lookup-in-scala.html> protocol.

I can’t think of a good “-able” word for this. "DynamicMemberLookupProtocol" is probably good enough IMO.

-Richard

···

On Nov 29, 2017, at 07:25, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:

On Nov 29, 2017, at 12:46 AM, Brent Royal-Gordon <brent@architechies.com> wrote:

On Nov 28, 2017, at 8:35 PM, Chris Lattner <clattner@nondot.org> wrote:

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?

Yes, this is implemented in the patch, it is a straight-forward additional check in protocol conformance.

Keep in mind that the compiler has other similar things for features that are not expressible in the Swift type system, e.g. the type(of:) function.

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.

I don’t really think that’s the case. Attributes have their place, for sure, but there in high precedent for Protocols providing exposing language features to types.

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:

I see the small advantage, but isn’t precedented at all. The ExpressibleBy protocols require redeclaring redundant methods just like this.

* If we eventually want to support multiple subscripts with different types (either by using different return types,

This is already supported in the patch, and the patch includes a test case.

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

I don’t see a connection between this rhetorical mechanic and the topic at hand :slight_smile:

-Chris

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Hi Chris,

Thank you for pushing this forward.

My only comment is that on the declaration side it would be great to also have an attribute to communicate that compiler magic is happening.

Currently it is surprising that a regular looking protocol is providing me so much power.

Suggestions:

@dynamic
struct PyVal : MemberLookupProtocol {...}

@dynamic
struct ParameterSummer : DynamicCallable {...}

// Error: This type needs the @dynamic attribute.
class ParamWinter : MyCustomCallableProtocolOrClassOrTypeAlias {...}

By requiring @dynamic (Or other attribute name), people can know that this is a compiler dynamic declaration and not just some random protocol whose name starts with Dynamic*. :slight_smile:

I’m not fond of the idea of an attribute. This introduce redundancy.

What a declaration means if the attribute is missing ?

Won’t compile?

What this attribute will mean on an other declaration ?

Won’t compile?

It would be similar to the swift 4 mode where @objc is required in some declarations.

···

On Dec 2, 2017, at 11:46 PM, Jean-Daniel <mailing@xenonium.com> wrote:

Le 3 déc. 2017 à 04:58, Jose Cheyo Jimenez via swift-evolution <swift-evolution@swift.org> a écrit :

If this attribute must be used with the declaration and it can’t be used with an other one, then what is the point of having an attribute but to exercice the compiler fixit feature

@NSManagedObject is another example I like from Core Data.
https://useyourloaf.com/blog/core-data-code-generation/

@NSManageObject apply to normal declarations that have a different meaning when this attribute is not present.

No it isn’t.
Like with @NSManagedObject, the very same declaration can exist with or without the @objc attribute depending the context, and the behavior of the function change. Neither is true with the @dynamic proposal.

···

Le 3 déc. 2017 à 09:29, Jose Cheyo Jimenez <cheyo@masters3d.com> a écrit :

On Dec 2, 2017, at 11:46 PM, Jean-Daniel <mailing@xenonium.com <mailto:mailing@xenonium.com>> wrote:

Le 3 déc. 2017 à 04:58, Jose Cheyo Jimenez via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> a écrit :

Hi Chris,

Thank you for pushing this forward.

My only comment is that on the declaration side it would be great to also have an attribute to communicate that compiler magic is happening.

Currently it is surprising that a regular looking protocol is providing me so much power.

Suggestions:

@dynamic
struct PyVal : MemberLookupProtocol {...}

@dynamic
struct ParameterSummer : DynamicCallable {...}

// Error: This type needs the @dynamic attribute.
class ParamWinter : MyCustomCallableProtocolOrClassOrTypeAlias {...}

By requiring @dynamic (Or other attribute name), people can know that this is a compiler dynamic declaration and not just some random protocol whose name starts with Dynamic*. :slight_smile:

I’m not fond of the idea of an attribute. This introduce redundancy.

What a declaration means if the attribute is missing ?

Won’t compile?

What this attribute will mean on an other declaration ?

Won’t compile?

It would be similar to the swift 4 mode where @objc is required in some declarations.

This is assuming that the protocol is what triggers the compiler to treat this type differently (Current draft implementation).

Currently in the draft there are 2-3 protocols that give the dynamic behavior to a type. These protocols can be typealias'ed, conformed by other protocols with different names etc.

···

On Dec 3, 2017, at 12:44 PM, Jean-Daniel <mailing@xenonium.com> wrote:

Le 3 déc. 2017 à 09:29, Jose Cheyo Jimenez <cheyo@masters3d.com <mailto:cheyo@masters3d.com>> a écrit :

On Dec 2, 2017, at 11:46 PM, Jean-Daniel <mailing@xenonium.com <mailto:mailing@xenonium.com>> wrote:

Le 3 déc. 2017 à 04:58, Jose Cheyo Jimenez via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> a écrit :

Hi Chris,

Thank you for pushing this forward.

My only comment is that on the declaration side it would be great to also have an attribute to communicate that compiler magic is happening.

Currently it is surprising that a regular looking protocol is providing me so much power.

Suggestions:

@dynamic
struct PyVal : MemberLookupProtocol {...}

@dynamic
struct ParameterSummer : DynamicCallable {...}

// Error: This type needs the @dynamic attribute.
class ParamWinter : MyCustomCallableProtocolOrClassOrTypeAlias {...}

By requiring @dynamic (Or other attribute name), people can know that this is a compiler dynamic declaration and not just some random protocol whose name starts with Dynamic*. :slight_smile:

I’m not fond of the idea of an attribute. This introduce redundancy.

What a declaration means if the attribute is missing ?

Won’t compile?

What this attribute will mean on an other declaration ?

Won’t compile?

It would be similar to the swift 4 mode where @objc is required in some declarations.

No it isn’t.
Like with @NSManagedObject, the very same declaration can exist with or without the @objc attribute depending the context, and the behavior of the function change. Neither is true with the @dynamic proposal.

________________________
**someModule.swift***

public protocol JustAnotherProtocol: DynamicCallable {...}
________________________

**myModule.swif***

struct JSVal: JustAnotherProtocol {...}
________________________

How do I know JustAnotherProtocol is dynamic in a git diff?

Having a common sigil like @IamDynamic can make all these declarations explicit. Clearer to the reader. All other related dynamic protocols can share the same umbrella sigil.

Chris added it to the list of potential solutions.
https://gist.github.com/lattner/b016e1cf86c43732c8d82f90e5ae5438#reducing-potential-abuse

"
We could add an attribute or use some other way to make conformance to DynamicMemberLookupProtocol more visible, e.g.:
@dynamic
struct PyVal : DynamicCallable {...}
"

Terms of Service

Privacy Policy

Cookie Policy