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

@Chris_Lattner3 Are there any plans for a standardized interface for bridging between Swift common currency types and those of the "guest language"?

I don't have anything planned, given that "guest" languages are all different, I'm not sure how far we can usefully get by trying to standardize across them.

The Python interop playground (which I've posted to swift-evolution a few times) uses existing language features (e.g. a PythonConvertible protocol) which works fine for these needs. Python interop does not require hooking into the "bridging" mechanics that Objective-C uses. That said, there are surely other design points that could be interesting, by the time we get to standardizing the Python interop I'm sure we'll consider that.

-Chris

This was extensively discussed in the pitch phase for both proposals. I can understand that you'd like to see the whole picture of how the two proposals interact - if that's the case, I encourage you to read the threads from those pitch phases, they are all archived and available online. The short version of it is that DynamicCallable provides hooks for two different things:

  1. A call of a value, taking a parameter list with values, optional parameter labels, etc, handling "x(1,2,3)".
  2. A method-style call of a value, with the stuff from above plus a basename, handling "x.base(1,2,3)".

A language like Python uses #1 and not #2. A language like Smalltalk uses #2 but not #1. Some smalltalky languages may want to also implement DynamicMemberLookup, e.g. in Ruby (IIRC) "x.foo" is equivalent to "x.foo()".

Given a type that implements #1, #2 and also conforms to DynamicMemberLookup (e.g. to handle the Ruby case), the following is ambiguous:

value.basename(arg)

which could either be resolved as (not the actual names):
value.callMethodStyle("baseName", arg)
or:
value.memberLookup("baseName").call(arg)

In this ambiguous case, the compiler will resolve towards the first, which is what makes smalltalky languages just naturally fall out.

This is all very simple and composes correctly, and I see no value in providing compound names for DynamicMemberLookup: that would be actively harmful for smalltalk languages. In any case, if you'd like full details, the pitch phase discussions contains them. Thanks!

-Chris

2 Likes

You're right. I missed the difference between DynamicCallableWithKeywordsToo and DynamicCallableKeywordedMethod. The later does the dynamic member lookup and the call in one operation (and has nothing to do with callable types really). I remember reading the discussion about it but didn't realize it had been added to the proposal. Sorry for the confusion.

Can this work with the part where you assign a function to a variable using a compound name like in my earlier example though? Perhaps this is unimportant as you can always wrap things in a closure of your own, but I still think it'd be a good idea to accept the full Swift syntax through dynamism and not just the subset that is convenient for integrating most foreign languages.

I do have 1 question on this proposal. Does this mean every dynamic properties must share the same return type for every properties? What is the case for something like one property returns String and another property returns Int?

I had question while scanning through the responses but didn’t see brought up. How will this affect autocomplete? I know this might be a concern to mostly Xcode but my understanding is that Xcode uses tools like SourceKit under the hood. If a type implements this protocol how will autocomplete behave? Autocomplete has been mediocre at best in my experience with Swift and I worry how this might effect the tooling that already exists.

1 Like

It seems likely there would be no autocomplete for dynamic properties, since there’s no compile time verification of whether they exist. I so it’ll be tons of fun with the autocomplete list getting polluted with all of the other possibilities.

There is nothing that technically prevents this, as you say, at the very least it could be sugar for the closure. We'll discuss the various tradeoffs in that discussion cycle.

-Chris

Correct, all dynamic properties have to have the same type. Usually, a dynamic currency type like "PyVal" that can contain either an int or a string, which is how dynamic languages model things.

-Chris

This is discussed in the proposal here: https://github.com/apple/swift-evolution/blob/master/proposals/0195-dynamic-member-lookup.md#future-directions-python-code-completion

-Chris

If the proposal behaves exactly like something[dynamicMember: "else"], you would also be able to implement the subscript multiple times with differing return types. This currently works:

struct S {
    subscript(_ i: Int) -> String { return "lol" }
    subscript(_ i: Int) -> Int { return 42 }
}

let s = S()
let str: String = s[0]
let int = s[0] as Int
1 Like

Lukas is correct, and there is a test case to that effect (showing context sensitive type inference). I apologize for the confusion if that is what you meant!

-Chris

Oh interesting? But isn't DynamicMemberLookupProtocol a protocol which must use an Associated Type for the returned value type?

If your type wraps PyVal, then dynamic member lookup would not be available to your users. But it’s true that if you extend PyVal, there’s nothing to ensure that someone is invoking your extension method and not some other method because of a typo. Is this what you’re concerned about?

I was not talking about extending PyVal. I was talking about the code that lives inside the implementation of a wrapper.

import dynamic is a partial solution to the request for usage-site annotation. It would be better than nothing but it is a very blunt instrument (file-at-a-time) and it does nothing to address the use of dynamic lookup on types declared within the same module. It would be a little bit odd but I suppose we could require import dynamic MyModule to enable dynamic lookup on types declared within the same module. One advantage of something along these lines is that I suspect most types implementing DynamicMemberLookupProtocol would not actually want dynamic lookup enabled on self within the scope of the implementation!

I would be willing to accept the bluntness of the file-at-a-time nature of this solution if it is a compromise that is more palatable to others who don't want usage-site annotation within individual blocks of code as long as it also addressed dynamic lookup on types declared within the same module. Perhaps it strikes the best balance of enabling the syntactic sugar for those who want it while still making it opt-in and leaving at least some indication in the source when this feature is in use.

This is explained extensively in the proposal, please refer to it, thanks!

-Chris

What I understand now is that, the DynamicMemberLookupProtocol will act like a marker protocol which doesn't have any requirement like normal protocol does. The compiler will only use it for lookup the subscription implementation.

This means that the Generic subscript declaration in DynamicMemberLookupProtocol will be possible

Is that correct?

I don't know what you mean. It is possible for protocols to have generic subscript requirements (i.e. subscript<T>(...) ), but they can only be fulfilled by a type with a generic subscript, so they aren't useful for this feature.

I see. Sorry, I was just a little bit confused. It's almost midnight here lol.
After another round of thinking and reading, I think I understand the case on the return type of this subscript
Thank you for the answer.

I maintain that it's important in this proposal to specify what happens when you say obj.foo(bar:), even without DynamicCallable. I think it's fine to say "this is not supported" and also "we may choose to support this in the future", possibly through DynamicCallable and possibly through something else, but it should be in the proposal.

I'm sorry if that wasn't clear. "This is not supported" by this proposal is the answer.

-Chris

1 Like