SE-0195 — Introduce User-defined "Dynamic Member Lookup" Types

If used strictly for writing wrapper classes, there would only be a single duplication in the case of settable properties, and even then it would be localised. Everything else should be a single-line callthrough to the dynamic method/property.

I would consider extended use of this feature outside of writing wrappers an abuse of this feature, and am concerned that this feature makes that far too easy.

I'm not sure I follow, see above.

I agree. This shouldn't be an issue, but only if the feature isn't abused. I'm not sure whether to give the proposal the benefit of the doubt in this regard.

An alternative to the dynamic {} syntax suggested, is only allowing dynamic accesses/calls within types marked @wrapper. This would have the benefit of hinting to the user the correct usage.

It depends. If you're just wrapping the type directly that is true. But I can imagine cases where the wrapper would be a lightweight facade that may use the same member of the underlying Python type in a few different places.

If the only purpose of this feature is to sugar code within direct wrappers of dynamic types I would be strongly opposed to it. Providing sugar for that limited context is not at all worth introducing a feature with such a broad scope.

I'm pretty sure the intent is that the feature will be used more liberally. There are a variety of contexts where that tradeoff might make sense (such as ad-hoc scripts and newcomers from other languages). That is why I am not opposed to the feature, I just want to see it designed in a way that reduces the potential harm to large teams comprised of developers of mixed skill levels.

3 Likes

Thought of one other thing the proposal does not account for: can dynamic members satisfy protocol requirements? I'd be inclined to say "no", mostly because I have no idea how it would work with types and also because that seems like magic that's likely to get someone into trouble. But maybe someone else has thought about it more. (It'd also be a compatible change to go from "no" to "under certain circumstances" in the future, since dynamic member lookup only kicks in after regular lookup fails.)

4 Likes

The Core Team met on Wednesday, January 31 to discuss the proposal and the feedback from the review. There's been a lot of great discussion, and the review thread unsurprisingly bifurcates on two important subtopics:

  1. Is this the right direction for Swift, and if so, is this the right general approach?

  2. Assuming the direction and approach is more-or-less correct, discussion about specific details (e.g., should the approach use a marker protocol).

After reviewing the discussion in this review thread and discussing this review within the Core Team, the Core Team has decided the answer to #1 is "yes". The proposal is accepted in principle, but specific details of the proposal need to be further discussed and ironed out. Specifically, there is the matter of using a marker protocol, which raises a bunch of technical questions.

On the general principle of the proposal, the Core Team felt that:

  • This proposal added valuable functionality to Swift
  • This proposal is not at odds at potentially adding any new dynamic affordances to Swift later that (say) tie into Swift's runtime metadata, etc.
  • There are tooling affordances, such as syntax coloring, that can be used to distinguish methods call going through this member lookup mechanism — without adding additional syntactic weight that would be at odds of some of the core goals of this proposal

@Chris_Lattner3 has a revised version of the proposal, which can be found here:

https://github.com/apple/swift-evolution/blob/master/proposals/0195-dynamic-member-lookup.md

The previous version of the proposal can be found here:

minor typo fixes. · apple/swift-evolution@59c7455 · GitHub

The review will extend for another week to discuss the revised details of the proposal.

Thank you to everyone who has participated in the review. The insights and feedback has been invaluable. The review will continue on this review thread.

11 Likes

Thanks @tkremenek! The revised proposal is substantially the same as the previous revision of the proposal, the only change is to resyntax it from being a marker protocol:

   struct YourType : DynamicMemberLookupProtocol { ... 

into an attribute:

  @dynamicMemberLookup
  struct YourType { ... }

The actual diff to the proposal is visible here if it helps. I wouldn't be surprised if I missed updating something minor, if so, let me know and I'll address it.

-Chris

4 Likes

I don't see any alternative like the following in the proposal, so I'm wondering if it had been considered we could just make it an attribute on the subscript itself?

struct PyVal {
  @dynamicMemberLookup
  subscript(pythonMember name: String) -> PyVal {
    ...
  }
}

It looks cleaner to me since the attribute is kept close to the member it refers to. Also, the name of the subscript parameter no longer needs to be follow a hard-coded value.

If the DynamicCallable proposal too is going to switch to an attribute form too, maybe the attribute sould be attached to the relevant member function too.

A brief reply on the revisions to the proposal—

I agree with the conclusion that the original marker protocol didn’t enable useful generic algorithms and that being able to use it as an existential doesn’t seem to offer much benefit.

But I would think that this observation is a good argument for adopting the alternative associated type design that Chris originally outlined. It would enable the spelling of an explicit protocol requirement. And, conceivably, a protocol that refines DynamicMemberLookupProtocol could then provide useful extension methods.

I rather think that such a state of affairs would be superior to the current one, where no such refinements are possible. Moreover, the aesthetics of @dynamicMemberLookup @dynamicCallable public struct PyVal is vastly inferior; it’s simultaneously less readable and more in-your-face, IMO.

1 Like

My review of the new proposal is brief: I don’t see strong advantage or disadvantage to either the marker protocol or the attribute. Both are fine. None of the pros and cons discussed seem particularly strong to me. I defer entirely to those who have opinions on the matter.

Thanks to Chris, Ted, and core team for patiently shepherding this forward.

1 Like

My original opinion still stands. I think this proposal is a good improvement for Swift. My only input for this small change is that @dynamicMemberLookup, while visually less appealing than a protocol, serves a better purpose.

The way the previous proposal was given, the marking protocol had no actual requirements defined inside. So the @dynamicMemberLookup does give some visual indication that this new functionality has some intrinsic stuff built into the compiler.

What's more, I believe this will possibly help alleviate some people's concerns that this functionality will spread to become a thorn in Swift projects. From my experience in the Swift community, even an additional @ or # is enough to give the novice or uninformed enough caution that they will go and lookup what the usage of this attribute is, and what it's meant for, rather than blindly slapping it on because it makes something "easier".

tl;dr: I'm fine with this change and am still +1

2 Likes
  1. Can protocols use this attribute? I am assuming no but wanted to confirm.

  2. Do subclasses inherit the attribute?

  3. Can it be mixed with @objc classes?

  4. Can nested types use the attribute?

1 Like

I don't have a super strong opinion about this, but my weak preference is to put this on the type. Rationale: this affects the global behavior of the type, meaning that we want the attribute to be easily visible as part of the type declaration (not buried under hundreds of lines of other members and methods). The subscript is an implementation detail of the behavior, but the behavior itself is the key thing to model.

Also, by requiring it on the type, it makes it immediately obvious that you cannot add this in an extension.

-Chris

4 Likes

Yes, Yes, Yes, Yes.

-Chris

1 Like

Hi Xiaodi,

The attribute still works with existentials. You can define something like:

@dynamicMemberLookup
protocol MyThing {
  func randomRequirement()

  subscript(dynamicMember: String) -> Self? { get set }
}

... and all types that conform to MyThing will get the dynamic behavior, and have their requirement spelled according to the protocol requirement - all without needing an explicit associatedtypes (which make existentials complicated). This sort of example was mentioned by Joe Groff, and is what really made me realize that this is better as an attribute that protocols can use, rather than be a protocol itself.

It is quite possible I'm misunderstanding you. If so, I apologize, please restate and I'll try again :-)

On aesthetics, the general style is to put attributes on the preceding line, I don't think this is unwieldy, but I can see how others would feel differently. In any case, actual use of the attribute is not going to be super common, so the concrete syntax is less important than getting the language model right (imo).

@dynamicMemberLookup @dynamicCallable 
public struct PyVal {
  ...
}

-Chris

2 Likes

I bit of a bikeshed, considering aesthetics:

how about :

@dynamic(memberLookup, callable)
public struct PyVal {
    //...
}
3 Likes

Works for me, I don't have a strong preference either way. This might conflate these with the 'dynamic' declmodifier in some people's minds, but I don't think that would be a significant concern.

What do other people think?

-Chris

1 Like

That's certainly more visually appealing than having multiple @ attributes. As for conflating with the dynamic modifier, it's been my experience that this modifier is not very common, so I'm not that worried about that.

My only worry is people would get confused as to what the difference between dynamic and @dynamic(...) are, and why they're separate.

2 Likes

How about changing @dynamic(...) to something that more broadly represents compiler magic? This can encompass any future additional magical markers:

@magic(memberLookup, callable)
// or
@magic(dynamicMemberLookup, dynamicCallable)
1 Like

I think magic is pretty interesting name choice. Personally I wouldn't be opposed, but it doesn't really seem very clear cut where and what @magic would possibly enclose. Would it only be used on types? Methods? Properties? Seems like something that would need its own proposal. And I'm not sure if we've seen precedent on introducing a feature, and then saying we'll bring up a new proposal to effectively clean up the syntax.

Are there any other similar magic protocols/modifiers/etc that would better be enclosed in a new @magic attribute?

I liked the original proposal, but I do feel that an attribute better reflects the relevant compiler magic than a protocol would. The ugliness of @dynamicMemberLookup is also a good indicator to people who don't know what it is that they should go look up its meaning when they encounter it. A protocol conformance is more easily ignored by newcomers. It should be used rarely enough that a bit of ugliness at the declaration site is no big deal.