Proposal: Introduce User-defined "Dynamic Member Lookup" Types

I suggest using different subscripts for function/method calls and properties:

While I perfectly understand why subscript is the best way to represent dynamic property, I don’t get how it help to abuse it to implement dynamic methods. Methods are not l-value, so why do you want to use a subscript for them ?

···

Le 28 nov. 2017 à 22:56, Ben Rimmington via swift-evolution <swift-evolution@swift.org> a écrit :

* `value(...)` ==> `value[dynamicFunction: ""](...)`
* `value.name(...)` ==> `value[dynamicFunction: "name"](...)`
* `value.name` ==> `value[dynamicProperty: "name"]`

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

I’d like to formally propose the inclusion of user-defined dynamic member lookup types.

Here is my latest draft of the proposal:
https://gist.github.com/lattner/b016e1cf86c43732c8d82f90e5ae5438
https://github.com/apple/swift-evolution/pull/768

An implementation of this design is available here:
Implement the DynamicLookupProtocol proposal. by lattner · Pull Request #13076 · apple/swift · GitHub

The implementation is straight-forward and (IMO) non-invasive in the compiler.

I think better interoperability with Python (and other OO languages in widespread use) is a good goal, and I agree that the implementation of the feature described is straight-forward and not terribly invasive in the compiler.

However, I do not think this proposal is going in the right direction for Swift. I have objections on several different grounds.

Philosophy
Swift is, unabashedly, a strong statically-typed language. We don’t allow implicit down casting, we require “as?” so you have to cope with the possibility of failure (or use “as!” and think hard about the “!”). Even the gaping hole that is AnyObject dispatch still requires the existence of an @objc declaration and produces an optional lookup result, so the user must contend with the potential for dynamic failure. Whenever we discuss adding more dynamic features to Swift, there’s a strong focus on maintaining that strong static type system.

IMO, this proposal is a significant departure from the fundamental character of Swift, because it allows access to possibly-nonexistent members (as well as calls with incorrect arguments, in the related proposal) without any indication that the operation might fail. It’s easy to fall through these cracks for any type that supports DynamicMemberLookupProtocol—a single-character typo when using a DynamicMemberLookupProtocol-capable type means you’ve fallen out of the safety that Swift provides. I think that’s a poor experience for the Python interoperability case, but more on that in the Tooling section below.

While we shouldn’t necessarily avoid a feature simply because it can be used distastefully, consider something like this:

  public extension NSObject : DynamicMemberLookupProtocol, DynamicCallableProtocol { … }

that goes directly to the Objective-C runtime to resolve member lookups and calls—avoiding @objc, bridging headers, and so on. It’s almost frighteningly convenient, and one could imagine some mixed Objective-C/Swift code bases where this would save a lot of typing (of code)… at the cost of losing static typing in the language. The presence of that one extension means I can no longer rely on the safety guarantees Swift normally provides, for any project that imports that extension and uses a subclass of NSObject. At best, we as a community decide “don’t do that”; at worse, some nontrivial fraction of the community decides that the benefits outweigh the costs (for this type or some other), and we can no longer say that Swift is a strong statically-typed language without adding “unless you’re using something that adopts DynamicMemberLookupProtocol”.

Tooling
The Python interoperability enabled by this proposal *does* look fairly nice when you look at a small, correctly-written example. However, absolutely none of the tooling assistance we rely on when writing such code will work for Python interoperability. Examples:

* As noted earlier, if you typo’d a name of a Python entity or passed the wrong number of arguments to it, the compiler will not tell you: it’ll be a runtime failure in the Python interpreter. I guess that’s what you’d get if you were writing the code in Python, but Swift is supposed to be *better* than Python if we’re to convince a community to use Swift instead.
* Code completion won’t work, because Swift has no visibility into declarations written in Python
* Indexing/jump-to-definition/lookup documentation/generated interface won’t ever work. None of the IDE features supported by SourceKit will work, which will be a significant regression for users coming from a Python-capable IDE.

Statically-typed languages should be a boon for tooling, but if a user coming from Python to Swift *because* it’s supposed to be a better development experience actually sees a significantly worse development experience, we’re not going to win them over. It’ll just feel inconsistent.

Dynamic Typing Features
It’s possible that the right evolutionary path for Swift involves some notion of dynamic typing, which would have a lot of the properties sought by this proposal (and the DynamicCallableProtocol one). If that is true—and I’m not at all convinced that it is—we shouldn’t accidentally fall into a suboptimal design by taking small, easy, steps. If we’re to include dynamic-typing facilities, we should look at more existing practice—C# ‘dynamic' is one such approach, but more promising would be some form of gradual typing a la TypeScript that let’s one more smoothly (and probably explicitly) shift between strong and weak typing.

How Should Python Interoperability Work?
Going back to the central motivator for this proposal, I think that providing something akin to the Clang Importer provides the best interoperability experience: it would turn Python declarations into *real* Swift declarations, so that we get the various tooling benefits of having a strong statically-typed language. Sure, the argument types will all by PyObject or PyVal, but the names are there for code completion (and indexing, etc.) to work, and one could certainly imagine growing the importer to support Python’s typing annotations. But the important part here is that it doesn’t change the language model at all—it’s a compiler feature, separate from the language. Yes, the Clang importer is a big gnarly beast—but if the goal is to support N such importers, we can refactor and share common infrastructure to make them similar, perhaps introducing some kind of type provider infrastructure to allow one to write new importers as Swift modules.

In truth, you don’t even need the compiler to be involved. The dynamic “subscript” operation could be implemented in a Swift library, and one could write a Python program to process a Python module and emit Swift wrappers that call into that subscript operation. You’ll get all of the tooling benefits with no compiler changes, and can tweak the wrapper generation however much you want, using typing annotations or other Python-specific information to create better wrappers over time.

Agreed.

In particular, I’d rather take the time to design a foundation for Swift interoperating with any given language, than have one mechanism for Python, another for Ruby, yet another for C++, etc.

- Dave Sweeris

···

Sent from my iPhone

On Nov 30, 2017, at 00:24, Douglas Gregor via swift-evolution <swift-evolution@swift.org> wrote:

On Nov 26, 2017, at 10:04 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:

I’d like to formally propose the inclusion of user-defined dynamic member lookup types.

Here is my latest draft of the proposal:
https://gist.github.com/lattner/b016e1cf86c43732c8d82f90e5ae5438
DynamicMemberLookup proposal by lattner · Pull Request #768 · apple/swift-evolution · GitHub

An implementation of this design is available here:
Implement the DynamicLookupProtocol proposal. by lattner · Pull Request #13076 · apple/swift · GitHub

The implementation is straight-forward and (IMO) non-invasive in the compiler.

I think better interoperability with Python (and other OO languages in widespread use) is a good goal, and I agree that the implementation of the feature described is straight-forward and not terribly invasive in the compiler.

However, I do not think this proposal is going in the right direction for Swift. I have objections on several different grounds.

+1 to everything Doug says here. He articulates the concerns I tried to voice in an earlier thread more clearly and more thoroughly.

This design introduces a significant hole in the type system which is contrary to the spirit of Swift. It doesn’t just make dynamic language interop easy, it also changes the semantics of any type which conforms to DynamicMemberLookupProtocol. That is not something that is locally visible when looking at a piece of code. Worse, it does so in a way that trivial mistakes such as a typo can turn into runtime errors. Worst of all, as Doug points out it is possible to use retroactive conformance to change the semantics of vast quantities of widely used types in one fell swoop.

One additional concern that I don’t believe has been mentioned is that of evolution. When a library is imported from a dynamic language and the library makes breaking changes Swift code written using this feature will break without notice until a runtime error occurs. Is that acceptable? That may be what users of dynamic languages are accustomed to but can we do better in Swift? If we can make the process of upgrading dependencies less a less harrowing experience perhaps that could be a major selling point in moving them to Swift. But we won’t accomplish that with this design.

Interop with dynamic languages is a worthy goal, but we should do it in a way that has no potential impact on code that does not need the feature. A semantic variant of “don’t pay for what you don’t use” should apply here. If we add this feature everyone will pay for it every time they read unfamiliar Swift code. They will have to contend with the potential for conformances to this protocol, even on standard library types. That feels like a pretty high cost to me, especially for programmers who aren’t using the dynamic language interop features themselves.

Further, a consistent theme has been the desire to avoid things like compiler flags that could fragment the community. I fear this feature has the potential to do that, especially if it is really successful at attracting people from the dynamic language community. I think we should consider carefully the following questions:

* Do we want to encourage these programmers to continue writing code in an extremely dynamic style when they come to Swift or do we want to encourage them to embrace the advantages of relying on the type system?
* If we want them to embrace the type system but that would keep them away from Swift how do we feel about that?

Matthew

···

Sent from my iPad

On Nov 30, 2017, at 2:24 AM, Douglas Gregor via swift-evolution <swift-evolution@swift.org> wrote:

On Nov 26, 2017, at 10:04 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:

Philosophy
Swift is, unabashedly, a strong statically-typed language. We don’t allow implicit down casting, we require “as?” so you have to cope with the possibility of failure (or use “as!” and think hard about the “!”). Even the gaping hole that is AnyObject dispatch still requires the existence of an @objc declaration and produces an optional lookup result, so the user must contend with the potential for dynamic failure. Whenever we discuss adding more dynamic features to Swift, there’s a strong focus on maintaining that strong static type system.

IMO, this proposal is a significant departure from the fundamental character of Swift, because it allows access to possibly-nonexistent members (as well as calls with incorrect arguments, in the related proposal) without any indication that the operation might fail. It’s easy to fall through these cracks for any type that supports DynamicMemberLookupProtocol—a single-character typo when using a DynamicMemberLookupProtocol-capable type means you’ve fallen out of the safety that Swift provides. I think that’s a poor experience for the Python interoperability case, but more on that in the Tooling section below.

While we shouldn’t necessarily avoid a feature simply because it can be used distastefully, consider something like this:

  public extension NSObject : DynamicMemberLookupProtocol, DynamicCallableProtocol { … }

that goes directly to the Objective-C runtime to resolve member lookups and calls—avoiding @objc, bridging headers, and so on. It’s almost frighteningly convenient, and one could imagine some mixed Objective-C/Swift code bases where this would save a lot of typing (of code)… at the cost of losing static typing in the language. The presence of that one extension means I can no longer rely on the safety guarantees Swift normally provides, for any project that imports that extension and uses a subclass of NSObject. At best, we as a community decide “don’t do that”; at worse, some nontrivial fraction of the community decides that the benefits outweigh the costs (for this type or some other), and we can no longer say that Swift is a strong statically-typed language without adding “unless you’re using something that adopts DynamicMemberLookupProtocol”.

Tooling
The Python interoperability enabled by this proposal *does* look fairly nice when you look at a small, correctly-written example. However, absolutely none of the tooling assistance we rely on when writing such code will work for Python interoperability. Examples:

* As noted earlier, if you typo’d a name of a Python entity or passed the wrong number of arguments to it, the compiler will not tell you: it’ll be a runtime failure in the Python interpreter. I guess that’s what you’d get if you were writing the code in Python, but Swift is supposed to be *better* than Python if we’re to convince a community to use Swift instead.
* Code completion won’t work, because Swift has no visibility into declarations written in Python
* Indexing/jump-to-definition/lookup documentation/generated interface won’t ever work. None of the IDE features supported by SourceKit will work, which will be a significant regression for users coming from a Python-capable IDE.

Statically-typed languages should be a boon for tooling, but if a user coming from Python to Swift *because* it’s supposed to be a better development experience actually sees a significantly worse development experience, we’re not going to win them over. It’ll just feel inconsistent.

Dynamic Typing Features
It’s possible that the right evolutionary path for Swift involves some notion of dynamic typing, which would have a lot of the properties sought by this proposal (and the DynamicCallableProtocol one). If that is true—and I’m not at all convinced that it is—we shouldn’t accidentally fall into a suboptimal design by taking small, easy, steps. If we’re to include dynamic-typing facilities, we should look at more existing practice—C# ‘dynamic' is one such approach, but more promising would be some form of gradual typing a la TypeScript that let’s one more smoothly (and probably explicitly) shift between strong and weak typing.

How Should Python Interoperability Work?
Going back to the central motivator for this proposal, I think that providing something akin to the Clang Importer provides the best interoperability experience: it would turn Python declarations into *real* Swift declarations, so that we get the various tooling benefits of having a strong statically-typed language. Sure, the argument types will all by PyObject or PyVal, but the names are there for code completion (and indexing, etc.) to work, and one could certainly imagine growing the importer to support Python’s typing annotations. But the important part here is that it doesn’t change the language model at all—it’s a compiler feature, separate from the language. Yes, the Clang importer is a big gnarly beast—but if the goal is to support N such importers, we can refactor and share common infrastructure to make them similar, perhaps introducing some kind of type provider infrastructure to allow one to write new importers as Swift modules.

In truth, you don’t even need the compiler to be involved. The dynamic “subscript” operation could be implemented in a Swift library, and one could write a Python program to process a Python module and emit Swift wrappers that call into that subscript operation. You’ll get all of the tooling benefits with no compiler changes, and can tweak the wrapper generation however much you want, using typing annotations or other Python-specific information to create better wrappers over time.

  - Doug

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

I’d like to formally propose the inclusion of user-defined dynamic member
lookup types.

Here is my latest draft of the proposal:
https://gist.github.com/lattner/b016e1cf86c43732c8d82f90e5ae5438
DynamicMemberLookup proposal by lattner · Pull Request #768 · apple/swift-evolution · GitHub

An implementation of this design is available here:
Implement the DynamicLookupProtocol proposal. by lattner · Pull Request #13076 · apple/swift · GitHub

The implementation is straight-forward and (IMO) non-invasive in the
compiler.

I think better interoperability with Python (and other OO languages in
widespread use) is a good goal, and I agree that the implementation of the
feature described is straight-forward and not terribly invasive in the
compiler.

However, I do not think this proposal is going in the right direction for
Swift. I have objections on several different grounds.

*Philosophy*
Swift is, unabashedly, a strong statically-typed language. We don’t allow
implicit down casting, we require “as?” so you have to cope with the
possibility of failure (or use “as!” and think hard about the “!”). Even
the gaping hole that is AnyObject dispatch still requires the existence of
an @objc declaration and produces an optional lookup result, so the user
must contend with the potential for dynamic failure. Whenever we discuss
adding more dynamic features to Swift, there’s a strong focus on
maintaining that strong static type system.

IMO, this proposal is a significant departure from the fundamental
character of Swift, because it allows access to possibly-nonexistent
members (as well as calls with incorrect arguments, in the related
proposal) without any indication that the operation might fail. It’s easy
to fall through these cracks for any type that supports
DynamicMemberLookupProtocol—a single-character typo when using a
DynamicMemberLookupProtocol-capable type means you’ve fallen out of the
safety that Swift provides. I think that’s a poor experience for the Python
interoperability case, but more on that in the Tooling section below.

While we shouldn’t necessarily avoid a feature simply because it can be
used distastefully, consider something like this:

public extension NSObject : DynamicMemberLookupProtocol,
DynamicCallableProtocol { … }

that goes directly to the Objective-C runtime to resolve member lookups
and calls—avoiding @objc, bridging headers, and so on. It’s almost
frighteningly convenient, and one could imagine some mixed
Objective-C/Swift code bases where this would save a lot of typing (of
code)… at the cost of losing static typing in the language. The presence of
that one extension means I can no longer rely on the safety guarantees
Swift normally provides, for any project that imports that extension and
uses a subclass of NSObject. At best, we as a community decide “don’t do
that”; at worse, some nontrivial fraction of the community decides that the
benefits outweigh the costs (for this type or some other), and we can no
longer say that Swift is a strong statically-typed language without adding
“unless you’re using something that adopts DynamicMemberLookupProtocol”.

*Tooling*
The Python interoperability enabled by this proposal *does* look fairly
nice when you look at a small, correctly-written example. However,
absolutely none of the tooling assistance we rely on when writing such code
will work for Python interoperability. Examples:

* As noted earlier, if you typo’d a name of a Python entity or passed the
wrong number of arguments to it, the compiler will not tell you: it’ll be a
runtime failure in the Python interpreter. I guess that’s what you’d get if
you were writing the code in Python, but Swift is supposed to be *better*
than Python if we’re to convince a community to use Swift instead.
* Code completion won’t work, because Swift has no visibility into
declarations written in Python
* Indexing/jump-to-definition/lookup documentation/generated interface
won’t ever work. None of the IDE features supported by SourceKit will work,
which will be a significant regression for users coming from a
Python-capable IDE.

Statically-typed languages should be a boon for tooling, but if a user
coming from Python to Swift *because* it’s supposed to be a better
development experience actually sees a significantly worse development
experience, we’re not going to win them over. It’ll just feel inconsistent.

*Dynamic Typing Features*
It’s possible that the right evolutionary path for Swift involves some
notion of dynamic typing, which would have a lot of the properties sought
by this proposal (and the DynamicCallableProtocol one). If that is true—and
I’m not at all convinced that it is—we shouldn’t accidentally fall into a
suboptimal design by taking small, easy, steps. If we’re to include
dynamic-typing facilities, we should look at more existing practice—C#
‘dynamic' is one such approach, but more promising would be some form of
gradual typing a la TypeScript that let’s one more smoothly (and probably
explicitly) shift between strong and weak typing.

*How Should Python Interoperability Work?*
Going back to the central motivator for this proposal, I think that
providing something akin to the Clang Importer provides the best
interoperability experience: it would turn Python declarations into *real*
Swift declarations, so that we get the various tooling benefits of having a
strong statically-typed language. Sure, the argument types will all by
PyObject or PyVal, but the names are there for code completion (and
indexing, etc.) to work, and one could certainly imagine growing the
importer to support Python’s typing annotations
<https://docs.python.org/3/library/typing.html&gt;\. But the important part
here is that it doesn’t change the language model at all—it’s a compiler
feature, separate from the language. Yes, the Clang importer is a big
gnarly beast—but if the goal is to support N such importers, we can
refactor and share common infrastructure to make them similar, perhaps
introducing some kind of type provider infrastructure to allow one to write
new importers as Swift modules.

Thanks for expressing those concerns. I think a lot of people like me don't
feel legitimate enough to argue with Chris on what Swift philosophy is
about, so it's good to see someone qualified saying it. One question though
: I'm not familiar with Clang importer, so there's something I don't
understand : in your ideal scenario, would the syntax for calling those
Python imported functions look like regular swift function calls ? Because
i'm pretty sure some python function signature can't be expressed with the
current swift syntax, unless we had those "dynamic" types that you
mentioned above ( in which case, are you suggesting adding a new keyword to
the language rather than a protocol ?)

···

On Thu, Nov 30, 2017 at 9:24 AM, Douglas Gregor via swift-evolution < swift-evolution@swift.org> wrote:

On Nov 26, 2017, at 10:04 PM, Chris Lattner via swift-evolution < > swift-evolution@swift.org> wrote:

In truth, you don’t even need the compiler to be involved. The dynamic
“subscript” operation could be implemented in a Swift library, and one
could write a Python program to process a Python module and emit Swift
wrappers that call into that subscript operation. You’ll get all of the
tooling benefits with no compiler changes, and can tweak the wrapper
generation however much you want, using typing annotations or other
Python-specific information to create better wrappers over time.

- Doug

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

Doug and others have brought up some great points, and I think Doug’s idea of a common infrastructure for importing declarations from other languages is _extremely_ attractive for the long-term future of Swift.

However, unlike this proposal, that will (I imagine as a non-compiler engineer) be a colossal undertaking, and as such it’s not going to make it into Swift 5, or possibly even 6 or 7. I understand, then, Chris’s (and other’s) desire to start interfacing with Python code now, not later. For me, far and away the biggest problem with this proposal (and the only outright deal-breaker) is that dynamic member lookups do not differentiate themselves in any way from statically-checked member lookups syntactically. I don’t object as strongly as others to the idea of adding this kind of dynamism to the language, but if it’s going to be there, it should not be possible to slightly misspell a static member name and end up with an unexpectedly dynamic member that may or may not fail at compile-time.

With all that being said, for people who are opposed to this proposal, how would the idea of an alternate, clearly opt-in syntax for dynamic member lookups change your views, if at all? I’m not sure what might provide the best balance of clarity and ergonomics, but we could always revive “->”, right? :P

···

On Nov 30, 2017, at 2:24 AM, Douglas Gregor via swift-evolution <swift-evolution@swift.org> wrote:

On Nov 26, 2017, at 10:04 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:

I’d like to formally propose the inclusion of user-defined dynamic member lookup types.

Here is my latest draft of the proposal:
https://gist.github.com/lattner/b016e1cf86c43732c8d82f90e5ae5438
DynamicMemberLookup proposal by lattner · Pull Request #768 · apple/swift-evolution · GitHub

An implementation of this design is available here:
Implement the DynamicLookupProtocol proposal. by lattner · Pull Request #13076 · apple/swift · GitHub

The implementation is straight-forward and (IMO) non-invasive in the compiler.

I think better interoperability with Python (and other OO languages in widespread use) is a good goal, and I agree that the implementation of the feature described is straight-forward and not terribly invasive in the compiler.

However, I do not think this proposal is going in the right direction for Swift. I have objections on several different grounds.

Philosophy
Swift is, unabashedly, a strong statically-typed language. We don’t allow implicit down casting, we require “as?” so you have to cope with the possibility of failure (or use “as!” and think hard about the “!”). Even the gaping hole that is AnyObject dispatch still requires the existence of an @objc declaration and produces an optional lookup result, so the user must contend with the potential for dynamic failure. Whenever we discuss adding more dynamic features to Swift, there’s a strong focus on maintaining that strong static type system.

IMO, this proposal is a significant departure from the fundamental character of Swift, because it allows access to possibly-nonexistent members (as well as calls with incorrect arguments, in the related proposal) without any indication that the operation might fail. It’s easy to fall through these cracks for any type that supports DynamicMemberLookupProtocol—a single-character typo when using a DynamicMemberLookupProtocol-capable type means you’ve fallen out of the safety that Swift provides. I think that’s a poor experience for the Python interoperability case, but more on that in the Tooling section below.

While we shouldn’t necessarily avoid a feature simply because it can be used distastefully, consider something like this:

  public extension NSObject : DynamicMemberLookupProtocol, DynamicCallableProtocol { … }

that goes directly to the Objective-C runtime to resolve member lookups and calls—avoiding @objc, bridging headers, and so on. It’s almost frighteningly convenient, and one could imagine some mixed Objective-C/Swift code bases where this would save a lot of typing (of code)… at the cost of losing static typing in the language. The presence of that one extension means I can no longer rely on the safety guarantees Swift normally provides, for any project that imports that extension and uses a subclass of NSObject. At best, we as a community decide “don’t do that”; at worse, some nontrivial fraction of the community decides that the benefits outweigh the costs (for this type or some other), and we can no longer say that Swift is a strong statically-typed language without adding “unless you’re using something that adopts DynamicMemberLookupProtocol”.

Tooling
The Python interoperability enabled by this proposal *does* look fairly nice when you look at a small, correctly-written example. However, absolutely none of the tooling assistance we rely on when writing such code will work for Python interoperability. Examples:

* As noted earlier, if you typo’d a name of a Python entity or passed the wrong number of arguments to it, the compiler will not tell you: it’ll be a runtime failure in the Python interpreter. I guess that’s what you’d get if you were writing the code in Python, but Swift is supposed to be *better* than Python if we’re to convince a community to use Swift instead.
* Code completion won’t work, because Swift has no visibility into declarations written in Python
* Indexing/jump-to-definition/lookup documentation/generated interface won’t ever work. None of the IDE features supported by SourceKit will work, which will be a significant regression for users coming from a Python-capable IDE.

Statically-typed languages should be a boon for tooling, but if a user coming from Python to Swift *because* it’s supposed to be a better development experience actually sees a significantly worse development experience, we’re not going to win them over. It’ll just feel inconsistent.

Dynamic Typing Features
It’s possible that the right evolutionary path for Swift involves some notion of dynamic typing, which would have a lot of the properties sought by this proposal (and the DynamicCallableProtocol one). If that is true—and I’m not at all convinced that it is—we shouldn’t accidentally fall into a suboptimal design by taking small, easy, steps. If we’re to include dynamic-typing facilities, we should look at more existing practice—C# ‘dynamic' is one such approach, but more promising would be some form of gradual typing a la TypeScript that let’s one more smoothly (and probably explicitly) shift between strong and weak typing.

How Should Python Interoperability Work?
Going back to the central motivator for this proposal, I think that providing something akin to the Clang Importer provides the best interoperability experience: it would turn Python declarations into *real* Swift declarations, so that we get the various tooling benefits of having a strong statically-typed language. Sure, the argument types will all by PyObject or PyVal, but the names are there for code completion (and indexing, etc.) to work, and one could certainly imagine growing the importer to support Python’s typing annotations. But the important part here is that it doesn’t change the language model at all—it’s a compiler feature, separate from the language. Yes, the Clang importer is a big gnarly beast—but if the goal is to support N such importers, we can refactor and share common infrastructure to make them similar, perhaps introducing some kind of type provider infrastructure to allow one to write new importers as Swift modules.

In truth, you don’t even need the compiler to be involved. The dynamic “subscript” operation could be implemented in a Swift library, and one could write a Python program to process a Python module and emit Swift wrappers that call into that subscript operation. You’ll get all of the tooling benefits with no compiler changes, and can tweak the wrapper generation however much you want, using typing annotations or other Python-specific information to create better wrappers over time.

  - Doug

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

I’d like to formally propose the inclusion of user-defined dynamic member lookup types.

Here is my latest draft of the proposal:
https://gist.github.com/lattner/b016e1cf86c43732c8d82f90e5ae5438
DynamicMemberLookup proposal by lattner · Pull Request #768 · apple/swift-evolution · GitHub

An implementation of this design is available here:
Implement the DynamicLookupProtocol proposal. by lattner · Pull Request #13076 · apple/swift · GitHub

The implementation is straight-forward and (IMO) non-invasive in the compiler.

Even the gaping hole that is AnyObject dispatch still requires the existence of an @objc declaration and produces an optional lookup result, so the user must contend with the potential for dynamic failure. Whenever we discuss adding more dynamic features to Swift, there’s a strong focus on maintaining that strong static type system.

...

How Should Python Interoperability Work?
Going back to the central motivator for this proposal, I think that providing something akin to the Clang Importer provides the best interoperability experience: it would turn Python declarations into *real* Swift declarations, so that we get the various tooling benefits of having a strong statically-typed language.

AnyObject doesn’t really come up too often in real-world Swift (in my experience, at least) exactly because of the Clang importer and the fact that Objective-C has adopted lots of annotations to make that importing experience better.

We can’t expect other languages to do that, but IMO we’ll still need some kind of importer/wrapper generator. When Swift was first opened to the public, people started porting their Objective-C code and asking themselves the philosophical question: “Is this Swift?” - i.e. are you using Swift’s features, or are you basically writing Objective-C code using a wrapper language?

Personally, I feel this system is designed to let you write Python, using Swift as a wrapper language - except unlike with Objective-C, it doesn’t have any static information about what you can do or not. It can’t provide the “safe by default” guarantees are other core principles and set Swift apart from other languages. When we consider interoperability with other languages, it should be from the perspective of mapping their features to Swift’s philosophies. This proposal takes the reverse approach, and makes Swift like Python, so I’m against it.

- Karl

···

On 30. Nov 2017, at 09:24, Douglas Gregor via swift-evolution <swift-evolution@swift.org> wrote:

On Nov 26, 2017, at 10:04 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

I’d like to formally propose the inclusion of user-defined dynamic member
lookup types.

Here is my latest draft of the proposal:
https://gist.github.com/lattner/b016e1cf86c43732c8d82f90e5ae5438
DynamicMemberLookup proposal by lattner · Pull Request #768 · apple/swift-evolution · GitHub

An implementation of this design is available here:
https://github.com/apple/swift/pull/13076

The implementation is straight-forward and (IMO) non-invasive in the
compiler.

I think better interoperability with Python (and other OO languages in
widespread use) is a good goal, and I agree that the implementation of the
feature described is straight-forward and not terribly invasive in the
compiler.

However, I do not think this proposal is going in the right direction for
Swift. I have objections on several different grounds.

*Philosophy*
Swift is, unabashedly, a strong statically-typed language. We don’t allow
implicit down casting, we require “as?” so you have to cope with the
possibility of failure (or use “as!” and think hard about the “!”). Even
the gaping hole that is AnyObject dispatch still requires the existence of
an @objc declaration and produces an optional lookup result, so the user
must contend with the potential for dynamic failure. Whenever we discuss
adding more dynamic features to Swift, there’s a strong focus on
maintaining that strong static type system.

IMO, this proposal is a significant departure from the fundamental
character of Swift, because it allows access to possibly-nonexistent
members (as well as calls with incorrect arguments, in the related
proposal) without any indication that the operation might fail. It’s easy
to fall through these cracks for any type that supports
DynamicMemberLookupProtocol—a single-character typo when using a
DynamicMemberLookupProtocol-capable type means you’ve fallen out of the
safety that Swift provides. I think that’s a poor experience for the Python
interoperability case, but more on that in the Tooling section below.

While we shouldn’t necessarily avoid a feature simply because it can be
used distastefully, consider something like this:

public extension NSObject : DynamicMemberLookupProtocol,
DynamicCallableProtocol { … }

that goes directly to the Objective-C runtime to resolve member lookups
and calls—avoiding @objc, bridging headers, and so on. It’s almost
frighteningly convenient, and one could imagine some mixed
Objective-C/Swift code bases where this would save a lot of typing (of
code)… at the cost of losing static typing in the language. The presence of
that one extension means I can no longer rely on the safety guarantees
Swift normally provides, for any project that imports that extension and
uses a subclass of NSObject. At best, we as a community decide “don’t do
that”; at worse, some nontrivial fraction of the community decides that the
benefits outweigh the costs (for this type or some other), and we can no
longer say that Swift is a strong statically-typed language without adding
“unless you’re using something that adopts DynamicMemberLookupProtocol”.

There are several commenters below to whom I would have liked to respond in
the fullness of time, but time constraints would make doing so prohibitive.
Since your message set off an abundance of discussion, I'll reply to the
points you make here and, along the way, ask your forbearance to bring up
and respond to some related concerns raised by others.

I agree that the prospect above seems not ideal at all. On reading Chris's
proposal, it never occurred to me that the intention was to support such
retroactive conformance to these special protocols. Admittedly, such
retroactive conformance is possible with all protocols--with the notable
exception of those that require compiler synthesis of requirements. But
Chris's protocols seemed magical enough (in the gut feeling sense) that I
naturally assumed that retroactive conformance was never on the table. We
would be justified in making that prohibition here, I think, although I'm
not sure if Chris as proposal author feels the same way.

Alternatively--and perhaps more elegantly--we could address this concern
head-on by having, instead of `DynamicMemberLookupProtocol` and
`DynamicCallable`, a magical class `DynamicObject` which all dynamic types
must inherit from. It would then be clear by design that Swift types cannot
be _retroactively dynamic_ in the sense that Chris proposes. I *think* the
vast majority of bridged use cases can tolerate being `final class` types
instead of `struct` types. I could be wrong though.

Now, as to the possibility of failure: I agree also that eliding the
possibility of lookup failure at the callsite requires further
consideration. Some might agree that restricting dynamic features only to
subclasses of a `DynamicObject` might be clear enough that we do not need
to go further in this regard. I think all will agree that inventing new (to
Swift) notations like "->" or "::" to denote dynamic lookup is rather
awkward and not ergonomic. I wonder if it would be instead sufficient to
require dynamic member lookup to return values of type `T!` (as in, IUOs).
IUOs are, after all, designed to deal with similar situations in bridging
from Obj-C, and are explicitly "transitional technology."

*Tooling*

The Python interoperability enabled by this proposal *does* look fairly
nice when you look at a small, correctly-written example. However,
absolutely none of the tooling assistance we rely on when writing such code
will work for Python interoperability. Examples:

* As noted earlier, if you typo’d a name of a Python entity or passed the
wrong number of arguments to it, the compiler will not tell you: it’ll be a
runtime failure in the Python interpreter. I guess that’s what you’d get if
you were writing the code in Python, but Swift is supposed to be *better*
than Python if we’re to convince a community to use Swift instead.
* Code completion won’t work, because Swift has no visibility into
declarations written in Python
* Indexing/jump-to-definition/lookup documentation/generated interface
won’t ever work. None of the IDE features supported by SourceKit will work,
which will be a significant regression for users coming from a
Python-capable IDE.

Statically-typed languages should be a boon for tooling, but if a user
coming from Python to Swift *because* it’s supposed to be a better
development experience actually sees a significantly worse development
experience, we’re not going to win them over. It’ll just feel inconsistent.

As I wrote in the earlier thread, I think this is the wrong way to reason
about the use case--and a counterproductive one that effectively rejects
the legitimacy of dynamic language interop support instead of working
towards an optimal solution for it. Along those lines, some replies to your
message literally question whether the user case is worthwhile to support
at all, which I think entirely misses the mark.

As Chris writes in his proposal, there are several areas (such as data
science) where, to put it plainly, Python or another dynamic language is
significantly better than Swift due to a much larger ecosystem of
libraries, tools, and user communities, with sometimes decades of lead
time. It is nonsensical to talk about how Swift is "supposed to be better"
in any way whatsoever in that context. As diehard Swift users, we may be
confident that the virtues of Swift's syntax, static typing, compiler
smarts, protocol-based design, or whatever else offer opportunities for,
say, data science libraries and tools to be better in the future,
eventually. But to make this even possible involves first making Swift a
viable language in which to work with current data science tools. Your top
bullet point here reasons that one key flaw of Chris's proposal is that he
has not somehow figured out how to make dynamic Python calls give
compile-time Swift errors. If this is the minimum bar for Python interop,
then as far as I can tell, it seems isomorphic to rejecting interop with
dynamic languages altogether.

It goes without saying that, in bridging between languages X and Y, much of
X's native tooling will be inoperable, and much of Y's native tooling will
not work. The solution is to build additional tools where necessary, not to
argue that interop shouldn't be implemented in the first place.

*Dynamic Typing Features*

It’s possible that the right evolutionary path for Swift involves some
notion of dynamic typing, which would have a lot of the properties sought
by this proposal (and the DynamicCallableProtocol one). If that is true—and
I’m not at all convinced that it is—we shouldn’t accidentally fall into a
suboptimal design by taking small, easy, steps. If we’re to include
dynamic-typing facilities, we should look at more existing practice—C#
‘dynamic' is one such approach, but more promising would be some form of
gradual typing a la TypeScript that let’s one more smoothly (and probably
explicitly) shift between strong and weak typing.

*How Should Python Interoperability Work?*
Going back to the central motivator for this proposal, I think that
providing something akin to the Clang Importer provides the best
interoperability experience: it would turn Python declarations into *real*
Swift declarations, so that we get the various tooling benefits of having a
strong statically-typed language. Sure, the argument types will all by
PyObject or PyVal, but the names are there for code completion (and
indexing, etc.) to work, and one could certainly imagine growing the
importer to support Python’s typing annotations
<https://docs.python.org/3/library/typing.html&gt;\. But the important part
here is that it doesn’t change the language model at all—it’s a compiler
feature, separate from the language. Yes, the Clang importer is a big
gnarly beast—but if the goal is to support N such importers, we can
refactor and share common infrastructure to make them similar, perhaps
introducing some kind of type provider infrastructure to allow one to write
new importers as Swift modules.

In truth, you don’t even need the compiler to be involved. The dynamic
“subscript” operation could be implemented in a Swift library, and one
could write a Python program to process a Python module and emit Swift
wrappers that call into that subscript operation. You’ll get all of the
tooling benefits with no compiler changes, and can tweak the wrapper
generation however much you want, using typing annotations or other
Python-specific information to create better wrappers over time.

I doubt seriously that there is any viable path to interoperating with a
much more established and extensive ecosystem which begins by requiring
that the more dominant ecosystem support Swift-specific annotations or
tooling. It would seem that, if we're to implement a Swift library to do
what you describe, it'd have to be one with extensive knowledge of both
Python and Swift, being able to parse entire Python modules as well as
create entire Swift ones. Since we haven't even designed a way of writing
code-generating macros for Swift in Swift, I struggle to see how this
hypothetical tool is going to be ever built, or whether sufficient people
exist with the expertise to do so, given that you'd need deep expertise
working in both languages in order to write the tool that permits you to
work in both languages.

···

On Thu, Nov 30, 2017 at 2:24 AM, Douglas Gregor via swift-evolution < swift-evolution@swift.org> wrote:

On Nov 26, 2017, at 10:04 PM, Chris Lattner via swift-evolution < > swift-evolution@swift.org> wrote:

Hi Doug,

Thank you for the detailed email. I have been traveling today, so I haven’t had a chance to respond until now. I haven’t read the down-thread emails, so I apologize if any of this was already discussed:

I think better interoperability with Python (and other OO languages in widespread use) is a good goal, and I agree that the implementation of the feature described is straight-forward and not terribly invasive in the compiler.

Fantastic, I’m really pleased to hear that! I only care about solving the problem, so if we can find a good technical solution to the problems than I’ll be happy.

A funny thing about swift-evolution is that it is populated with lots of people who already use Swift and want to see incremental improvements, but the people who *aren’t* using Swift yet (but should be) aren’t represented here. As you know, I’m perhaps the biggest proponent of Swift spreading into new domains and earning the love of new users.

However, I do not think this proposal is going in the right direction for Swift. I have objections on several different grounds.

Thanks for being up front about this. It turns out that a majority of the points you raise were brought up early in the pitch phases of the discussion, but I screwed up by not capturing that discussion into the alternatives section of the proposal.

Better late than never I guess: I just significantly revised the proposal, adding a new “Alternative Python Interoperability Approaches” section. I’d appreciate it if you could read it and comment if you find any part disagreeable:
https://gist.github.com/lattner/b016e1cf86c43732c8d82f90e5ae5438#alternative-python-interoperability-approaches

Many of the points you make are discussed there, but I’ll respond point-by-point below as well:

Philosophy
Swift is, unabashedly, a strong statically-typed language.

That’s factually incorrect.

We don’t allow implicit down casting, we require “as?” so you have to cope with the possibility of failure (or use “as!” and think hard about the “!”). Even the gaping hole that is AnyObject dispatch still requires the existence of an @objc declaration and produces an optional lookup result, so the user must contend with the potential for dynamic failure.

AnyObject dispatch is one of the gaping holes for sure. You don’t mention it, but its lookup results are represented as *ImplicitlyUnwrappedOptional* results, which do not force the user to contend with dynamic failure. Further, unlike my proposal (which is fully type safe), AnyObject lookup is not type safe at all. It is also far more invasively intertwined throughout the compiler.

More problematically for your argument: your preferred approach requires the introduction of (something like) DynamicMemberLookupProtocol or something like AnyObject-For-Python, so your proposal would be additive on top of solving the core problem I’m trying to solve. It isn’t an alternative approach at all.

Whenever we discuss adding more dynamic features to Swift, there’s a strong focus on maintaining that strong static type system.

Which this does, by providing full type safety - unlike AnyObject lookup.

IMO, this proposal is a significant departure from the fundamental character of Swift, because it allows access to possibly-nonexistent members (as well as calls with incorrect arguments, in the related proposal) without any indication that the operation might fail.

The only way your claim is correct is if someone implements the protocol wrong. What you describe is true of AnyObject lookup though, so I understand how you could be confused by that.

It’s easy to fall through these cracks for any type that supports DynamicMemberLookupProtocol—a single-character typo when using a DynamicMemberLookupProtocol-capable type means you’ve fallen out of the safety that Swift provides.

Since you seem to be latching on to this, I’ll say it again: the proposal is fully type safe :-)

That said, the “simple typo” problem is fundamental to the problem of working with a dynamically typed language: unless you can eradicate all “fundamental dynamism” from the language, you cannot prevent this.

That said, since this is a problem inherent with these languages, it is something people are already very familiar with, and something that everyone using those APIs has had to deal with. This is also not our problem to solve, and people in the Python community have been discussing and working on it over a large part of its 25 year history. I find your belief that we could solve this for Python better than the Python community itself has both idealistic and a bit naive.

While we shouldn’t necessarily avoid a feature simply because it can be used distastefully, consider something like this:

  public extension NSObject : DynamicMemberLookupProtocol, DynamicCallableProtocol { … }

that goes directly to the Objective-C runtime to resolve member lookups and calls—avoiding @objc, bridging headers, and so on.

Yes, this feature (like just about everything) can be misused in Swift. If someone actually did that, they’d find out rapidly that it is a really bad thing to do for lots of reasons, some of which you point out.

That said, I find it far more likely that someone would pervasively use IUOs everywhere or use AnyObject for everything. This is something that real users really do, and while it is unfortunate, it is impractical to prevent all forms of abuse.

Tooling
The Python interoperability enabled by this proposal *does* look fairly nice when you look at a small, correctly-written example. However, absolutely none of the tooling assistance we rely on when writing such code will work for Python interoperability. Examples:

* As noted earlier, if you typo’d a name of a Python entity or passed the wrong number of arguments to it, the compiler will not tell you: it’ll be a runtime failure in the Python interpreter. I guess that’s what you’d get if you were writing the code in Python, but Swift is supposed to be *better* than Python if we’re to convince a community to use Swift instead.
* Code completion won’t work, because Swift has no visibility into declarations written in Python
* Indexing/jump-to-definition/lookup documentation/generated interface won’t ever work. None of the IDE features supported by SourceKit will work, which will be a significant regression for users coming from a Python-capable IDE.

Yes, welcome to the realities of modern Python development!

Statically-typed languages should be a boon for tooling, but if a user coming from Python to Swift *because* it’s supposed to be a better development experience actually sees a significantly worse development experience, we’re not going to win them over. It’ll just feel inconsistent.

By your argument we should ban AnyObject lookup as well, given its inconsistency with the rest of the language.

I’m glad you mention the idea of users coming from Python to Swift: by many estimates, the Python community is *two* orders of magnitude or more larger than the Swift community - and lets not mention other dynamic languages like Javascript. It really is high value to make the path from Python to Swift nicer, and if we do so, I believe people will find lots of reasons to write new Swift code to get all the advantages that you mention (and more).

Dynamic Typing Features
It’s possible that the right evolutionary path for Swift involves some notion of dynamic typing, which would have a lot of the properties sought by this proposal (and the DynamicCallableProtocol one). If that is true—and I’m not at all convinced that it is—we shouldn’t accidentally fall into a suboptimal design by taking small, easy, steps.

Given that you haven’t followed the discussion on the many threads we’ve had on this, and haven’t proposed a workable approach to this problem, I’m not sure upon what basis your fears and uncertainty and doubt are founded.

How Should Python Interoperability Work?
Going back to the central motivator for this proposal, I think that providing something akin to the Clang Importer provides the best interoperability experience: it would turn Python declarations into *real* Swift declarations, so that we get the various tooling benefits of having a strong statically-typed language.

This could be an theoretically interesting refinement to this proposal but I’m personally very skeptical that this is every going to happen. I’ve put the rationale into the alternatives section of the proposal. I don’t explain it in the proposal in this way directly, but I believe it is far more likely for a Pythonista transplant into Swift to rewrite their code in Swift than it is to use Python type annotations.

Sure, the argument types will all by PyObject or PyVal,

That’s the root of the problem. Python has the “fully dynamic” equivalent of “id” in Objective-C, so we need to represent that somehow. Even if we followed the implementation approach of the Clang importer, we would need some way to represent this dynamic case. That type needs features like DynamicMemberLookup or AnyObject. In my opinion, the DynamicMemberLookup approach is better in every way than AnyObject is.

Which approach do you think is the best way to handle the untyped “actually dynamic” case?

but the names are there for code completion (and indexing, etc.) to work, and one could certainly imagine growing the importer to support Python’s typing annotations <https://docs.python.org/3/library/typing.html&gt;\.

You’re basing this on the flawed assumption that local variables will pervasively have types, which I can’t imagine being the case. Even on "typable” API, I wouldn’t expect people to commonly get code completion results for reasons now explained in the proposal.

But the important part here is that it doesn’t change the language model at all—it’s a compiler feature, separate from the language. Yes, the Clang importer is a big gnarly beast—but if the goal is to support N such importers, we can refactor and share common infrastructure to make them similar, perhaps introducing some kind of type provider infrastructure to allow one to write new importers as Swift modules.

Sure, doing something like this could be an interesting direction to consider after getting basic fully dynamic support in.

In earlier threads, we had extensive discussion about type providers, and discussed that they don’t solve the problem for the same reasons that Clang importer doesn’t solve the problem: you still need the way to “provide” the fully dynamic type.

If you have to do that, it is perfectly reasonable to get the basic dynamic case in, then weigh the cost/benefit advantage of getting more static typing in if available somehow. While importing progressive typing annotations could be practical for Javascript interop (for example) I’m pretty skeptical it would be widely used for Python (again, rationale laid out in the proposal).

In truth, you don’t even need the compiler to be involved. The dynamic “subscript” operation could be implemented in a Swift library, and one could write a Python program to process a Python module and emit Swift wrappers that call into that subscript operation. You’ll get all of the tooling benefits with no compiler changes, and can tweak the wrapper generation however much you want, using typing annotations or other Python-specific information to create better wrappers over time.

I’d love for you to sketch out how any of this works with an acceptable user experience, because I don't see anything concrete here.

You seem to be thinking that there is some world where Python isn’t actually a dynamic language, or where the Python community switches to pervasive adoption of type hints (something that is unlikely to happen for a ton of reasons). Python is less statically typable than Objective-C is, even ignoring the constraints on the problem, and yet Objective-C has AnyObject - why do you think that Python wouldn’t need it? Since it needs it, then we need to design it: how do you think it should work?

-Chris

The examples in the proposal are very shallow, highlighting single lookups. But what are the types of the returned values?

The Python example is recursive: PyVal is returned and is the source of dynamism, so it chains arbitrarily.

I encourage you to check out the Playground I sent out on Nov 19 to swift-evolution which shows how all this works in more detail.

I think the proposal should include a more complete example that demonstrates how meaningful interop code could be written.

The proposal is independent of any specific bridge, I covered one possible way that Python could be bridged in that playground. That said, the actual design of a Python bridge will itself be a follow-on proposal.

2. The JavaScript example will be popular, but isn’t actually that useful

Swift already has `Codable`, which provides more automatic and more type-safe (in that it encourages all-or-nothing decoding) JSON decoding.

This only helps you if you control the JSON format or if it has a principled structure that maps nicely to Swift. If you’re working with some existing service which is poorly structured and inconsistent, then it is very useful.

That said, the JSON example is simply an example to illustrate the ideas in the proposal, it is not part of the proposal itself.

-Chris

···

On Nov 30, 2017, at 3:46 AM, Matt Diephouse <matt+swift-evolution@diephouse.com> wrote:

Hi Chris,

I am definitely in favor of providing dynamic features in Swift, and of being able to interoperate easily with dynamic languages. I really like the idea overall.

I was about to write up a different idea I had for partially mitigating some of the issues around being able to mistype method names, etc…, but then I remembered a usability principle that I first heard from members of the Lisa team (discovered the hard way): Things which behave the same should look the same, and things which behave differently need to look different.

I like that the change in behavior is really limited in scope by this proposal, but there still isn’t really any indication other than the type (which might be inferred) that the normal checking behavior is offline. When in a fully dynamic environment, this isn’t really a big problem because you use different debugging strategies, but I worry that the free mixture of static and dynamic without any indicators showing the difference will lead to much more frequent errors.

What do you think about having a special method ‘dynamic' defined in this protocol, such that the compiler magic lookup behavior is only available when chained after that method? Thus:

  dog.add_trick(“sit”)

would have to be:

  dog.dynamic.add_trick(“sit”)

Now, I know that sounds like a pain in the butt, but it also buys you an extra ability (in addition to the extra cue that we have to watch out for dynamic issues). Because the dynamic lookup only happens for calls chained after dynamic, we now have normal swift lookup for all of the other methods/properties defined on the type (with autocomplete support, etc…).

What this means is that it is easy to wrap commonly used calls in a normal swift method:

  func addTrick(_ name:String) {
    self.dynamic.add_trick(name)
  }

Now the user can say the following with full autocomplete support, type checking, etc…

  addTrick(“sit”)

…and if they misspell it, they will get an error from the compiler. Error handling could also optionally be added to a wrapper. Most importantly, conversion to/from swift return types and parameters can easily be built into the wrappers.

The end result is a system where python code can be up and running in Swift very quickly (by using chaining with .dynamic), and then made more native Swift friendly incrementally over time (by adding wrappers/extensions for common/important calls). The only extra cost is having to type an extra word for dynamic calls without wrappers.

Thoughts?

Thanks,
Jon

It could work, but is a much worse approach IMO. The proposal discusses it here:
https://gist.github.com/lattner/b016e1cf86c43732c8d82f90e5ae5438#motivation-and-context
https://gist.github.com/lattner/b016e1cf86c43732c8d82f90e5ae5438#direct-language-support-for-python-and-all-the-other-languages

Doug has been suggesting similar directions upthread, when he has a chance to respond to my questions, I’ll do a detailed comparison of the approaches.

-Chris

···

On Dec 4, 2017, at 10:05 AM, Dave DeLong <swift@davedelong.com> wrote:

Hi Chris,

We do the Swift integration with Objective-C via modules. Why wouldn’t that approach work with Python? Or if it would, why would we favor this dynamic member lookup over importing a Python API via a module?

Dave

On Nov 26, 2017, at 11:04 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:

I’d like to formally propose the inclusion of user-defined dynamic member lookup types.

Here is my latest draft of the proposal:
https://gist.github.com/lattner/b016e1cf86c43732c8d82f90e5ae5438
DynamicMemberLookup proposal by lattner · Pull Request #768 · apple/swift-evolution · GitHub

An implementation of this design is available here:
https://github.com/apple/swift/pull/13076

The implementation is straight-forward and (IMO) non-invasive in the compiler.

-Chris

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

Hi Chris,

We do the Swift integration with Objective-C via modules. Why wouldn’t that approach work with Python? Or if it would, why would we favor this dynamic member lookup over importing a Python API via a module?

My understanding is that we would then have to write modules for each and every dynamic language people would want to import. It would take a lot more time/maintenance than this approach which theoretically allows for any dynamic language to become interoperable with much less effort and very little maintenance.

···

On Dec 4, 2017, at 11:05 AM, Dave DeLong via swift-evolution <swift-evolution@swift.org> wrote:

Dave

On Nov 26, 2017, at 11:04 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:

I’d like to formally propose the inclusion of user-defined dynamic member lookup types.

Here is my latest draft of the proposal:
https://gist.github.com/lattner/b016e1cf86c43732c8d82f90e5ae5438
https://github.com/apple/swift-evolution/pull/768

An implementation of this design is available here:
Implement the DynamicLookupProtocol proposal. by lattner · Pull Request #13076 · apple/swift · GitHub

The implementation is straight-forward and (IMO) non-invasive in the compiler.

-Chris

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

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

Awesome. The thread has gotten so long and forked that it’s hard to keep track of what has been asked and answered. I’ll check these out and get back if I have more questions.

Thanks! :bowing_man:

Dave

···

On Dec 4, 2017, at 11:20 AM, Chris Lattner <clattner@nondot.org> wrote:

It could work, but is a much worse approach IMO. The proposal discusses it here:
https://gist.github.com/lattner/b016e1cf86c43732c8d82f90e5ae5438#motivation-and-context
https://gist.github.com/lattner/b016e1cf86c43732c8d82f90e5ae5438#direct-language-support-for-python-and-all-the-other-languages

Doug has been suggesting similar directions upthread, when he has a chance to respond to my questions, I’ll do a detailed comparison of the approaches.

-Chris

On Dec 4, 2017, at 10:05 AM, Dave DeLong <swift@davedelong.com <mailto:swift@davedelong.com>> wrote:

Hi Chris,

We do the Swift integration with Objective-C via modules. Why wouldn’t that approach work with Python? Or if it would, why would we favor this dynamic member lookup over importing a Python API via a module?

Dave

On Nov 26, 2017, at 11:04 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

I’d like to formally propose the inclusion of user-defined dynamic member lookup types.

Here is my latest draft of the proposal:
https://gist.github.com/lattner/b016e1cf86c43732c8d82f90e5ae5438
DynamicMemberLookup proposal by lattner · Pull Request #768 · apple/swift-evolution · GitHub

An implementation of this design is available here:
Implement the DynamicLookupProtocol proposal. by lattner · Pull Request #13076 · apple/swift · GitHub

The implementation is straight-forward and (IMO) non-invasive in the compiler.

-Chris

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

There is nothing unsafe about this proposal. It is fully type safe and supports failable operations (by having the properties typed as optionals). This is shows by example in the proposal.

Your explanation above is so confusing to me. The closest analog to this feature is AnyObject dynamic lookup, which *is* completely unsafe, and is pervasively tangled throughout the compiler. Maybe you’re confusing the two.

-Chris

···

On Nov 27, 2017, at 8:57 AM, Mathew Huusko V <mhuusko5@gmail.com> wrote:

You're saying that there is universally no inherent difference, and that all calls "determine if you have called it" correctly, but then picked one of only a handful of cases in current practice where that is actually true. Yes "+" (/other math operators) and array access are unsafe, but most other things in Swift are safe by default, and you have to opt into un-safety (e.g. forcing or checking an optional or throwing call) — this is a main tenant of the language.

You're saying that there is universally no inherent difference, and that
all calls "determine if you have called it" correctly, but then picked one
of only a handful of cases in current practice where that is actually true.
Yes "+" (/other math operators) and array access are unsafe, but most other
things in Swift are safe by default, and you have to opt into un-safety
(e.g. forcing or checking an optional or throwing call) — this is a main
tenant of the language.

Perhaps I was not totally clear while mixing and matching my
observations/interpretations of safety and fallibility in compile vs.
runtime and readability vs. writability in my initial email, but I believe
the spirit of my concern was clear: there is obviously a difference between
dynamic and static languages/calls, and having the syntax the same is
misleading in a language centered around static safety, perhaps to a degree
where it is ergonomically counter productive.

···

On Mon, Nov 27, 2017 at 4:12 PM, Magnus Ahltorp <map@kth.se> wrote:

> 27 Nov. 2017 22:38 Mathew Huusko V via swift-evolution <
swift-evolution@swift.org> wrote:
>
> I tuned out the initial discussions of this proposal because there
seemed to be a lot of noise centered around implementation/maintainability.
I'm curious if the actual premise of the syntactic/sugar conversion has
been discussed/debated yet? i.e. making dynamic/stringly calls look like
normal calls is very clean, but it's also very misleading (by definition;
they're not normal/safe/checked calls) with a potential net reduction in
ergonomics.

There is nothing that is inherently non-fallible with "normal" Swift
calls. As far as the caller can tell, any function or method you call can
fail. There is no difference here; the implementation of the "user-defined
dynamic member lookup" object will determine if you have called it in a
proper way or not, in the same way as "+" will determine if you have called
it in a way that overflows or not.

/Magnus

You can never expect everything that is type safe to be non-failing. Also, the only two alternatives I feel would be acceptable to you is to either require that all return values from dynamic languages are optionals or to require a fully name and type specified layer between the dynamic language and Swift.

The first one would be non-ergonomic, since unwrapping would have to be done for every call and member access, maybe with the exception of direct use of implicitly unwrapped ones. This would likely be rejected by most users, since the need to check the existence of a member every time will seem unnecessary to them.

The second one will either place the burden of writing that layer on the API provider (which likely doesn't care about Swift) or the user of the API (which likely just wants to use the API without spending the time studying the API in that amount of detail and then writing the required code/rules).

Neither of these seem like acceptable alternatives to me.

Also, if the bridge author wants to return optionals all the time, that is possible, right?

/Magnus

···

28 Nov. 2017 00:57 Mathew Huusko V <mhuusko5@gmail.com> wrote:

You're saying that there is universally no inherent difference, and that all calls "determine if you have called it" correctly, but then picked one of only a handful of cases in current practice where that is actually true. Yes "+" (/other math operators) and array access are unsafe, but most other things in Swift are safe by default, and you have to opt into un-safety (e.g. forcing or checking an optional or throwing call) — this is a main tenant of the language.

Perhaps I was not totally clear while mixing and matching my observations/interpretations of safety and fallibility in compile vs. runtime and readability vs. writability in my initial email, but I believe the spirit of my concern was clear: there is obviously a difference between dynamic and static languages/calls, and having the syntax the same is misleading in a language centered around static safety, perhaps to a degree where it is ergonomically counter productive.

FYI this is not the definition of "safe" that Swift uses. Deliberately halting the process in response to an error counts as "safe". This is what Swift's arithmetic overflow and out of bounds array access do.

"Unsafe" is when incorrect code both does something wrong and also doesn't halt the process immediately. For example, using something like UnsafeMutablePointer to perform an unsafe memory access that is incorrect might read or write random memory and might do so without crashing.

Swift often prefer throws or optional returns instead of runtime checks that halt the process, but as you noted it does not always do so. I don't know if we have a good phrase analogous to "safe"/"unsafe" for the presence/absence of a runtime check that can halt the process.

···

On Nov 27, 2017, at 8:57 AM, Mathew Huusko V via swift-evolution <swift-evolution@swift.org> wrote:

You're saying that there is universally no inherent difference, and that all calls "determine if you have called it" correctly, but then picked one of only a handful of cases in current practice where that is actually true. Yes "+" (/other math operators) and array access are unsafe, but most other things in Swift are safe by default, and you have to opt into un-safety (e.g. forcing or checking an optional or throwing call) — this is a main tenant of the language.

--
Greg Parker gparker@apple.com <mailto:gparker@apple.com> Runtime Wrangler

Hi Chris,

“post.author" always invokes the DynamicMemberLookupProtocol proposal.
“post.author()” would invoke the “DynamicCallableWithKeywordsToo” hook in the dynamic callable proposal:
https://gist.github.com/lattner/a6257f425f55fe39fd6ac7a2354d693d

Elsewhere in Swift, it is generally the case that

foo.bar()

is equivalent to

let fn = foo.bar
fn()

Would this equivalence still hold with your proposal(s)?

Slava

···

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

First, I tried overloading `subscript(dynamicMember:)` by return type.

subscript(dynamicMember name: String) -> (_ arguments: Any...) -> JSValue
subscript(dynamicMember name: String) -> JSValue

It allows for method calls without the DynamicCallable proposal.

context[dynamicMember: "Math"][dynamicMember: "pow"](2, 53)
//-> 9007199254740992

context[dynamicMember: "Math"][dynamicMember: "PI"] as JSValue
//-> 3.1415926535897931

context[dynamicMember: "Math"][dynamicMember: "PI"]
//ERROR: Ambiguous use of 'subscript(dynamicMember:)'

This evolved into the differently labelled subscripts,
to avoid having to disambiguate by return type.

But you're right, a function might be better than a subscript.

-- Ben

···

On 28 Nov 2017, at 22:06, Jean-Daniel <mailing@xenonium.com> wrote:

While I perfectly understand why subscript is the best way to represent dynamic property, I don’t get how it help to abuse it to implement dynamic methods. Methods are not l-value, so why do you want to use a subscript for them ?

Chris wrote:

Paul wrote:

An “always use parens” bridge to Ruby has bad ergonomics
Zero-arg Ruby methods are a mixture of property-like things that would certainly not use parens in Swift, and function-like things that certainly would:

    // Idiomatic Swift:
    post.author.name.reversed()

    // Swift bridging to Ruby…

    // …if no-args methods •must• use parens:
    post.author().name().reverse()

    // …if no-args methods •can’t• use parens:
    post.author.name.reverse

If the goal is to make Swift mostly feel like Swift even when bridging to Ruby, then the bridge needs to support both access forms.

Ok, that can certainly be implemented by the two proposals I have in flight. No obvious problem there.

Chris, can you elaborate? I think the proposal precludes this; I must be missing something!

There are two proposals in flight: one that allows specifying runtime behavior for “a.b” syntax and one that allows specifying runtime behavior for “a(x)” and “a.b(x)” syntax (“DynamicCallableWithKeywordsToo”, which needs to be renamed obviously).

Yes, I’m aware of the two proposals in flight, but apparently missed the updated DynamicCallable proposal.

You must be talking about DynamicCallableKeywordedMethod, not DynamicCallableWithKeywordsToo? Yes, that third protocol would solve the problem!

Is DynamicCallableKeywordedMethod an official part of the callable proposal? I was thrown by the fact that the “Smalltalk Family Languages” section is not nested under “Proposed Solution,” and thought it was meant as a separate/alternative idea. But reading the text, I see that it does seem to be part of that proposal.

If that’s correct, then that “Smalltalk family” section ought to clarify that DynamicCallableKeywordedMethod takes precedence over DynamicMemberLookupProtocol. In other words, if `a` implements both protocols, then this:

    a.b(x)

…expands to this:

    a.dynamicCall(method: "b", arguments: [("", x)])

…and not this:

    a[dynamicMember: "b"].dynamicCall(arguments: [("", x)])

…while this:

    a.b

…still expands to:

    a[dynamicMember: "b”]

Cheers, P

For your use case, you’d implement both of them. If someone wrote

  a.b().c

Then you’d look up and call a zero-argument method named “b” on the a object. However if someone wrote:

  a.b.c

Then you’d do the same thing (in the implementation of the DynamicMemberLookupProtocol). This approach allows the human writing code at your bridge to use whatever syntax “feels best” to them given the specific API in question that they are using.

You could even have “a.b” syntax fall back to finding Ruby ivars as well, though I don’t know if that is actually a good idea in practice for a Ruby bridge.

It’s certainly not the behavior you’d want, no. Ruby devs make a clear distinction between attr readers (Ruby’s name for properties) and ivars (which are same-instance-private).

···

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

On Nov 28, 2017, at 10:12 AM, Paul Cantrell <cantrell@pobox.com <mailto:cantrell@pobox.com>> wrote:

As I read the proposal, the dynamic member subscript for post.author returns either a property value or a DynamicCallable, depending on whether the thing is a property or a method — but post.author and post.author() would both look identical to that subscript implementation, and there’s no distinction on the Ruby side, so the subscript can’t know which one to return.

“post.author" always invokes the DynamicMemberLookupProtocol proposal.
“post.author()” would invoke the “DynamicCallableWithKeywordsToo” hook in the dynamic callable proposal:
https://gist.github.com/lattner/a6257f425f55fe39fd6ac7a2354d693d

-Chris