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

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”.

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.

If you have an opinion, please speak up. The core team will continue to articulate our views (both as individuals and collectively as the core team) and make the final decisions for the Swift project, but we don’t have the breadth of experience of the larger Swift community, so we value discussion with everyone here.

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 ?)

Those imported functions would be regular Swift declarations, with implementations that call into the Python runtime. They’ll necessarily have very weakly-specified types, e.g.,

    def add_trick(self, trick):
        self.tricks.append(trick)

might turn into

  extension PyVal {
     func add_trick(_ trick: PyVal) { /* … */ }
  }

Python allows positional arguments, so it works to use the underscore. Python 3’s keyword-only arguments <https://www.python.org/dev/peps/pep-3102/&gt; could be mapped to proper Swift argument labels. Python’s **kwargs would be mapped to a dictionary, e.g.,

  func do_something(_ kwargs: [String: PyVal]) { … }

  - Doug

···

On Nov 30, 2017, at 3:24 AM, Benjamin G <benjamin.garrigues@gmail.com> wrote:
On Thu, Nov 30, 2017 at 9:24 AM, Douglas Gregor via swift-evolution <swift-evolution@swift.org <mailto: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:

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 <mailto: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.

As noted at the end of my message, I think one can write a Swift library to make working with the Python runtime much easier, and write a wrapper generator that produces Swift APIs from Python code that use said Swift library.

  - Doug

···

On Nov 30, 2017, at 1:01 PM, Zach Wolfe <zacharyreidwolfe@gmail.com> wrote:

On Nov 30, 2017, at 2:24 AM, Douglas Gregor via swift-evolution <swift-evolution@swift.org <mailto: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”.

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.

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 <mailto: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.

As noted at the end of my message, I think one can write a Swift library to make working with the Python runtime much easier, and write a wrapper generator that produces Swift APIs from Python code that use said Swift library.

That might be true for Python; I’m quite certain it’s not the case for Ruby or JS as they exist in the wild. However…

At the risk of stating a universally unpopular opinion, beyond interop with dynamic languages such as Python, there’s also value in dynamic member lookup for •purely Swift-centric• reasons.

I say this with mixed feelings. I share Douglas’s concerns about breaking Swift’s safety bubble. I like it that the language generally makes it clear •at the point of use• which language features have runtime pitfalls that the compiler cannot check. The points where this is not true of the language (e.g. array subscripts) always feel a bit gritty between the teeth.

There can, however, be great value in constructing type membership at runtime. The ergonomic access to JSON that’s been discussed in this proposal is nothing to sneeze at. Dynamic languages make for lovely DSLs in a way that fully statically type checked languages never do. Things like Rails’s programmatically generated named routes (e.g. “user_post_path(id)” because routing configuration created a user resource with nested posts) are possible to approximate with statically typed approaches — but only clumsily, never with the same expressiveness and concision.

I don’t mean to sneeze at the downside of dynamic typing either. They too are nothing to sneeze at. I’ve spent many years on both sides of the dynamic divide, and all I see is difficult tradeoffs.

Swift so far has said that dynamism isn’t worth the tradeoffs; it’s aggressively avoided even robust runtime reflection, much less dynamic dispatch. That may indeed be the right call for the language — but I’m not easily convinced there isn’t room to open the door wider.

I think of Chris’s post back in the early days of this list about the “programmer model” the language creates, and wonder if there’s a way we can’t make a model that leaves the door open to this kind of dynamic dispatch where it makes sense for the people and situation at hand, while still staying true to the language’s spirit of “compile-time safety by default, exceptions to that by intent at point of use.”

Opening the door to dynamism might take some of the flavor of the way the language currently handles unsafe memory access: it’s possible and even reasonably ergonomic, but you always know when you’ve crossed the Rubicon into Dangerland.

Cheers,

Paul

···

On Nov 30, 2017, at 3:40 PM, Douglas Gregor via swift-evolution <swift-evolution@swift.org> wrote:

On Nov 30, 2017, at 1:01 PM, Zach Wolfe <zacharyreidwolfe@gmail.com <mailto:zacharyreidwolfe@gmail.com>> wrote:

  - Doug

On Nov 30, 2017, at 2:24 AM, Douglas Gregor via swift-evolution <swift-evolution@swift.org <mailto: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”.

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.

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 <mailto: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

+1 to the concerns voiced by Doug, Matthew and others. I , too, would prefer a generator which generates a statically typed bridge instead of the dynamic lookup ature proposed which effectively undermines Swift's static type system.

-Thorsten

···

Am 30.11.2017 um 15:15 schrieb Matthew Johnson via swift-evolution <swift-evolution@swift.org>:

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:

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.

+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

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

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

Sent from my iPad

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.

+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?

FWIW +1 for Doug, Matthew and others said.

I believe it is not possible to express each variation of other language's syntax/behavior in Swift - in any case languages will differ in some details, function/properties calling/assignment rules etc. Also, we need to remember Swift exceptions, proposed 'async' feature, KeyPaths etc and map these to features of selected dynamic language. In result we'll have not-Swift-not-Python/whatever frankenstein code :-)

Probably it is better just to allow Python/other code inside swift code, be able to pass Swift instances and return some "result" from that code to Swift and be able to convert it then to standard Swift types.

// special syntax for other language's code
// raw dynamic language's code after "in" and before last bracket
// can have IDE/editor support for that language with all features standard for that language, like autocomplete
let code = {(name: String)->[Int] throws in @language(Python)
  // some raw Python code, indent is required
}
let result = try? code(name: "John")

or at least

// special syntax for other language's code
// raw dynamic language's code after "\\**PYTHON" line and before "**//"
// can have IDE/editor support for that language with all features standard for that language
// "\\**" and "**//" are fictional syntax for "raw" strings(without escaping, no interpolation)
let code = \\**PYTHON
  // some raw Python code
**//
// type(of: code) == String, 'PYTHON' is just allowed comment which can be used by IDE/editor
let result = try? PythonEngine.execute(code, externalParams: ["name": "John"]) as [Int]

(the syntax was not carefully thought out.)

IMO if we actually need such kind of interop with dynamic languages, it should be in very explicit form, with special syntax, saying "this 'thing' will be resolved dynamically, at run time, nothing is guaranteed in compile time, be aware".

Just my 2 cents.
Vladimir.

···

On 30.11.2017 17:15, Matthew Johnson via swift-evolution wrote:

On Nov 30, 2017, at 2:24 AM, Douglas Gregor via swift-evolution <swift-evolution@swift.org > <mailto: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:

Matthew

*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.

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 <mailto: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

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.

Doug’s email was pretty confusing, so I’ll try to clear up some of the misconceptions here. I also updated the proposal with a new section at the end, please check it out.

This design introduces a significant hole in the type system which is contrary to the spirit of Swift.

There is no “hole” in the type system. The proposal is fully type safe.

Further, the addition is absolutely in the spirit of Swift, because it is directly analogous to an existing feature: AnyObject lookup. The difference between it and AnyObject lookup is that AnyObject lookup is:

a) specific to Objective-C interop
b) type unsafe
c) massively invasive on the rest of the compiler.

We’ve made many attempts to narrow the scope of AnyObject lookup, but it is difficult to do so given backwards compatibility constraints and the limited nature of Objective-C metadata.

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.

This is a feature, like many others, which can be misused. This has not been the metric we have used to judge what should go into Swift. I can elaborate if my response to Doug wasn’t clear.

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.

How would you this to work with *any* interoperability approach? Dynamic languages are in fact dynamic, and need the ability to do dynamic lookups somehow. Those dynamic lookups will all have the problem you observe.

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.

You seem to fear that people will pervasively adopt this and use it for weird things. While that is possible, that would be simply one way to misuse Swift. What makes it more likely for someone to do this than to use unsafe features incorrectly, providing an apparently type safe API that actually isn’t?

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 really fail to see how this concern relates here. This has nothing to do with compiler flags. This has the potential to expand the community, not fragment it.

-Chris

···

On Nov 30, 2017, at 6:15 AM, Matthew Johnson <matthew@anandabits.com> wrote:

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”.

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.

FWIW, if retroactive conformance itself is a concern, then I’d be perfectly fine restricting the proposal a bit, and just say that DynamicMemberLookup has to be declared on a *primary type declaration* and not an extension. That seems perfectly reasonable, and I agree that “Dynamic behavior” could be considered part of the primary behavior of a type that shouldn't be retroactively added.

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.

I’d really like to use this thing with structs, so it seems better to limit it to primary declarations to solve this concern.

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. Y

This is a really great summary - and an example of the challenge of swift-evolution, whose members far over-represent “current” swift users, and far under-represents “future” ones. :-)

-Chris

···

On Nov 30, 2017, at 3:54 PM, Xiaodi Wu <xiaodi.wu@gmail.com> 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.

While I, too, am interested in attracting new users to Swift, it should not come at the cost of making the language less coherent.

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.

You’re going to have to explain that statement without reference to AnyObject (we’ll discuss that case below).

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.

As noted, AnyObject is a gaping hole, and the fact that it still produces an ImplicitlyUnwrappedOptional does make my statement weaker. It means you can dynamically end up with “unrecognized selector” without having acknowledged the possibility with a ‘!’.

It is also far more invasively intertwined throughout the compiler.

Not so much any more; there’s the weird lookup rule and some expression kinds that get mapped directly through to objc_msgSend.

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.

I wouldn’t say that’s my preferred approach. My preferred approach involves taking the method/property/etc. declarations that already exist in Python and mapping them into corresponding Swift declarations so we have something to find with name lookup. One could put all of these declarations on some PyVal struct or PythonObject and there would be no need for AnyObject-for-Python or DynamicMemberLookupProtocol.

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.

You get dynamic safety because it goes into the Python interpreter; fair enough. You get no help from your tools to form a correct invocation of any method provided by Python.

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.

AnyObject lookup still requires you to find an actual declaration with a type signature. Yes, there are still failure cases. The dynamic type might be totally unrelated to the class in which you found the declaration you’re supposedly calling, which is a typical “unrecognized selector” failure and will always be an issue with dynamic typing. The actual type safety hole you’re presumably referring to is that the selector could be overloaded with a different type signature, and we don’t proactively check that the signature we type-checked against matches the signature found at runtime. It’s doable with the Objective-C method encodings, but has never been considered worthwhile.

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.

I’m not pretending we can fully solve the problem. I’m pointing out that depending entirely on DynamicMemberLookupProtocol throws away information about method declarations that is present in Python and used by Python tooling to good effect.

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!

Python plugins for IDEs (e.g., for Atom) provide code completion, goto definition, and other related features. None of the Swift tooling will work if Swift’s interoperability with Python is entirely based on DynamicMemberLookupProtocol.

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.

By my argument, we should at least replace the ImplicitlyUnwrappedOptional result with a true Optional (so one has to acknowledge that the method shouldn’t be there). We’ve seriously considered it, but it’s a source-breaking change, and it hasn’t seemed worth the engineering effort to pursue it.

I don’t think that removing AnyObject dispatch entirely is possible at this point in Swift’s lifetime. While AnyObject has become much less prominent than it was in the Swift 1.0 days ([AnyObject] and [NSObject : AnyObject], oh my!), there is still a significant amount of code using it in the wild.

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.

A few meta-comments here. First of all, following all previous threads on a discussion is not realistic. This is part of the reason why we have different stages in a proposal’s lifetime, and is the responsibility of the proposal’s authors to capture alternatives and rationale in the proposal to make it self-contained. This proposal went through rapid iteration in the pitch phase and has now been elevated to a pull request to ask for formal review—you should expect more people to come on board having not read those threads. I appreciate that you have now captured more alternatives and rationale in the proposal.

Second, it is absolutely reasonable to disagree with the technical direction of a proposal without providing a complete solution to the problem that the proposal is attempting to solve. Some problems aren’t worth solving at all, or fully.

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.

I assume that this belief is based on type annotations lack of traction in the Python community thus far?

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.

The AnyObject approach has the advantage of knowing the set of declared, reachable APIs:

* Code completion shows all of the APIs that are possible to use via dynamic dispatch, with their signatures so can fill in the right # of arguments, see the names of the parameters, see documentation, etc.
* Indexing/refactoring/goto definition all point you to the declarations that could be the targets of dynamic dispatch

The DynamicMemberLookup approach is better for cases where you don’t have a declaration of the member you want to access. I suspect that’s not the common case.

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

AnyObject already exists in the language, and it fits the untyped “actually dynamic” case well. It does require having a declaration for the thing you want to reference, which I consider to be important: we can code-complete those declarations, goto-definition to see those declarations, index/refactor/look up documentation based on those declarations.

I’d be more inclined to push for the ImplicitlyUnwrappedOptional -> Optional change if we did something to make AnyObject more prominent in Swift.

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.

Remember that one *does* get code completion results for member access into an AnyObject… lots of them… but the list filters down pretty fast when you type a few characters, and then you get a member access that’ll fill in stubs for (say) the arguments to the method you were trying to call. But you can’t get those code completion results without having declarations to complete to.

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).

I’m saying we can do this instead of fully dynamic support; see below.

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.

We don’t need the basic dynamic case in the language to do this experiment. Take the PyVal struct from the proposal. Now, write a Python script that loads some module Foo and uses Python’s inspect <https://docs.python.org/3/library/inspect.html&gt; module to go find the classes, methods, etc., and pretty-print Swift code that uses PyVal. So this:

  def add_trick(self, trick):

turns into

  extension PyVal {
    func add_trick(_ trick: PyVal) -> PyVal {
      /* do the magic to call into Python */
    }
  }

Using the inspect module, you can extract parameter names, default arguments, docstrings, and more to reflect the existing Python API as Swift API, packed into a bridging module.

Note that we have a “flat” namespace of all Python methods on PyVal, which is basically what you get with AnyObject today. Swift tooling will provide code completion for member accesses into PyVal. Goto definition will jump to the pretty-printed declarations, which could have the docstrings formatted in comments and would show up in QuickHelp. The types are weak (everything is PyVal), but that’s what we expect from importing a dynamically-typed language.

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?

Dynamic typing doesn’t have to mean “give up on all static checking”; it means that we don’t restrict the set of operations that the language permits you to apply to a given value based on the static type of that value. The PyVal extensions I describe above, like AnyObject, give you access to all declared APIs to which we can dynamically dispatch.

  - Doug

···

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

Personally, I feel this system is designed to let you write Python, using Swift as a wrapper language - except unlike with Objective-C,

Let me paraphrase the proposal—it basically says this system is designed to make it easier to “fake import” existing Python/Ruby/JS libs to make it easier for people who depend on them to adopt Swift. Especially in the world of server-side Swift, I can see how this could help speed Swift’s adoption.

However, I believe that you bring up an extremely valid concern. To extrapolate from your points, this proposal seems to have a high potential to erode what makes Swift special, and it provides a way to get around Swift’s safe-guards, avoid writing Swift versions of libraries, and ultimately, avoid hiring real Swift developers.

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.

Exactly.

Look how JSON parsing is done in Swift 3.2 and 4.0—we make static types. The 1’s and 0’s from a web response go through layers of proper error handling before they become proper types Swift.

So why does this proposal keeps mentioning JSON parsing as an excuse for making a dynamic ghetto within the language? Because some people are parsing untyped JSON? Well, the solution for that is, type your frickin’ JSON.

Why compromise Swift’s static-ness and roll out the red carpet for the dynamic, unwashed masses?

Wouldn’t it be better to create an AI that quickly translates Pyton, Ruby, or JS into Swift? Augustus built huge public bathhouses for the unwashed Romans.

When we consider interoperability with other languages, it should be from the perspective of mapping their features to Swift’s philosophies.

Amen.

This proposal takes the reverse approach, and makes Swift like Python, so I’m against it.

This is my concern as well.

Perhaps there is a way to make the proposal require that you declare Swift types and protocols that map to these other languages, with verbose error handling for when this glue breaks down (just like we have for typed JSON).

Then, we could also make scripts that auto-generate the necessary Swift typing boilerplate interface code from the Python/Ruby/JS source, kinda like we get for Obj.-C bridging headers. This could be a good step towards the full AI translation that I mentioned.

Of course, maybe I am missing something here, and my opinion is wrong; Chris will let us know if these concerns are valid.

However, I hope we don’t hear how we are just being paranoid, because I assure you, this is not paranoia. It’s just about maintaining the static-ness and purity of Swift to help save the world from bad code.

Everything in life and engineering is a trade-off. Swift’s trademark is that it consistently sacrifices the convenience of dynamic-ness for the safety and performance of static-ness. This proposal does seem to do the opposite, and we’d be naive not to fear the effect this will have.

Ask yourself... what if Product knows about it? The last conversation a tech lead or engineering manager wants to have, I would imagine, is to explain on deaf ears why it would be bad to cut costs by farming out the iOS work to a cheap Python shop overseas, then just wrap it in sugary compiler hooks. Or why it would be bad for a Swift project to depend upon a stack of third-language dependencies that CocoaPods/Carthage can’t manage, XCode can’t analyze, stack traces can’t delve into (or can it?), lldb can’t step through, etc.

I do not believe this is unreasonable paranoia, I just believe it looks that way to people who work with an elite squad of engineers at a top company, who have perhaps not seen what goes on at most places, where budgets are tight and lots of people are not the biggest fans of Apple. It’s already with much grinding of teeth that non-Windows, non-Linux devs are necessary.

Given Swift’s emphasis on safety, this push to make danger easier really does surprise me, unless I just don’t understand this correctly. Have we re-evaluated the importance of having the language itself prohibit bad practices? Or is there some reason why this proposal does not actually make it easier to write code that looks like good, type-safe Swift code, but actually isn’t?

Finally, to really be honest, may I ask why web devs, many of whom never touched a line of Swift, deserve all this dynamic sugar, while on the other hand, we SWIFT devs don’t get any? How will people feel who were told that dynamic is so unsafe that Swift was designed to prevent it altogether? People who have been through all the migrations and member-wise trenches might chafe a bit. Is that unreasonable?

Lastly, if you can “fake import” Javascript or Python to Swift via this proposal, is there anything that would prevent the “fake import” of masquerading Swift modules? In other words, could lazy devs who want hacky dynamic-ness abuse this? If so then maybe I’m in favor after all ;P (Just kidding.)

- Jon

PS—I am just trying to think critically here. Ultimately this proposal would not hurt me, but I am just worried about the big picture. I can see why it’s a tough call and I am sure that some pressures exist that might make even this compromise to be worth it. I just feel like we should not underestimate how much bad will come along with the good, nor should we assume, “Well that kind of bad would never happen where I work, and only idiots would abuse this.”

···

On Nov 30, 2017, at 08:10, Karl Wagner via swift-evolution <swift-evolution@swift.org> wrote:

Hello, I agree with this reasoning.

Unless I miss the point, I see C. Lattner's proposals, function calls and member lookup, as a very restricted interface to dynamic languages. They do not aim at embedding dynamic languages into Swift at all, and let users write long Python/Ruby/JS programs in Swift.

For example, "real" python would need list comprehension, generators, async/await, etc. Such features are clearly out of scope of the proposals.

We can thus expect serious programs that use those features to process in three steps: 1 turn Swift values in foreign values, 2. process foreign values with a restricted foreign API, 3. turn computed foreign values into Swift values. It's all about using good tools when needed.

In this context, I too am concerned with retroactive conformance. If it were forbidden, it would make it very clear where the dynamic scope starts and ends:

  // Input
  let swiftInt: Int = 12

  // Enter Python
  let pyInt = Python.Value(swiftInt)
  
  // Python code
  let pyResult = pyInt.someFunc()
  
  // Back to Swift
  if let swiftResult = Int(pyResult) {
    // proceed
  }

Gwendal Roué

···

Le 1 déc. 2017 à 00:54, Xiaodi Wu via swift-evolution <swift-evolution@swift.org> a écrit :

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.

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."

I love the idea of a DynamicObject type instead of a protocol.

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.

I agree with everything that Xiaodi said. I believe that while it’s noble to want to bring more type information (ala TypeScript) to dynamic languages, we still need a simple way to dynamically call *at runtime* into dynamic languages. That may cause runtime errors, but that’s what dynamic languages are all about. I think it’s illusory to think that their nature can be profoundly changed. Even TypeScript, which brings all this type information to JavaScript still needs a way to call into non-typed interfaces: the compiler will be helpless in protecting the user from some runtime errors, but that’s the price you pay.

···

On 1 Dec 2017, at 00:54, Xiaodi Wu via swift-evolution <swift-evolution@swift.org> wrote:

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:

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.

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.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

I don’t really appreciate that argument. You also need to consider: what improvements are we offering these people who don’t yet use Swift?

For Objective-C developers, the static typing and resulting performance and safety improvements made this a worthwhile switch. Plus, we could interoperate very well with existing Objective-C codebases and libraries (e.g. we can have generic subclasses of NSObject!), so we could actually use a lot of these nice features when writing our mac/iOS applications.

For Python developers, I gather the pitch would be similar: Swift has all kinds of nice features which will make your code safer and faster. We’re unlikely to achieve the same tight integration with Python (or any other language) that we have with Objective-C, though. We used to rely a lot on AnyObject, but since Objective-C made the importing experience so much better, you rarely (if ever) see it in practice. Objective-C can also be more dynamic than we show it as - you can have methods which are not declared anywhere, and which the object synthesises during its message-handling routines. We don’t support calling these non-existing methods on Objective-C objects, so why should we allow it for these objects?

My worry is that in eagerness to try and scoop up developers of other languages, that we diminish the Swift language to the point where it doesn’t really provide a benefit for them. Python developers will be writing the same unsafe, non-checked code using Swift that they do in Python; where’s the win?

- Karl

···

On 1. Dec 2017, at 07:05, 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.

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.

Great!

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.

That’s a good principle. However, a dynamic member lookup is just a member lookup. By that principle, it should look like a member lookup :-)

Further, I incorporated some of the conversation with Matthew into the proposal, showing how adding even a single sigil to dynamic member lookup to distinguish it is problematic:
https://gist.github.com/lattner/b016e1cf86c43732c8d82f90e5ae5438#increasing-visibility-of-dynamic-member-lookups

Further, adding something like .dynamic would completely undermind the proposal. You can already write:

  x.get(“foo”).get(“bar”)

having to write:

  x.dynamic.foo.dynamic.bar

has no point.

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)
  }

This would require wrapping all calls for them to be usable.

-Chris

···

On Dec 3, 2017, at 5:45 AM, Jonathan Hull <jhull@gbis.com> wrote:

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?

I guess „dynamic“ isn’t a common property name, but imho such magic doesn’t feel right, even when there are no collisions in real-world code.

  let y = np^.arange?^(24)?^.reshape^?(2, 3, 4)!

That looks quite ugly… but imho that’s at least partially because of the choice of character
let y = np:arange?:reshape?:(2, 3, 4)!
doesn’t seem to be as bad as the use of carets.

But still, you would have to jump through some hoops to use both kinds of dispatch side by side (because you always have to think about how to call a method).
On the other hand, we also have &inoutParameter and try, which makes some things less convenient — and I’d say that dynamic dispatch is somehow comparable to inout-parameters (both shouldn’t be used if you can get around it).

So, why do people want differentiate?
I can see two major motivations for a different syntax:

a) you want to dynamic calls to stand out — which also could be done by tools:

let y = np.arange?(24)?.reshape?(2, 3, 4)! // let the editor use a different font/color for non-static stuff

b) you want to be sure that in a given situation, no dynamic dispatch is used — which could be solved by keeping the dot as general way to call something, and add an alternative that will only use static lookup

No matter what direction is taken here, imho this whole story has a really gigantic impact on the shape of Swift, and I have strong concerns if the two changes are reviewed in a normal way and added this year… there’s no experience with the proposed changes yet, and afaics, it has become quite hard to correct additions that turn out to be not as positive as expected.

- Tino

> 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.

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.

I've seen people in this thread mention AnyObject dynamic lookup as
something similar to the proposal. But there's something i don't understand
:
var a: AnyObject
a.anyFunc()

is a compiler error.

Whereas
var a: PyObject (or anything conforming to DynamicLookup depending on the
option from the proposal)
a.anyFunc()

would be perfectly fine. What's the part i didn't get ?

···

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

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

-Chris

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

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.

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.

Right.

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.

Note that this proposal is safe by both definitions, because (as is shown in the proposal) an implementation of the protocol can implement the dynamic member as returning optional. This provides a 100% safe interface to the client that doesn’t not "halt the process".

-Chris

···

On Nov 28, 2017, at 5:58 PM, Greg Parker via swift-evolution <swift-evolution@swift.org> wrote:

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

Yes, absolutely. An example of that is already in the proposal.

-Chris

···

On Nov 27, 2017, at 9:03 PM, Magnus Ahltorp <map@kth.se> wrote:
Also, if the bridge author wants to return optionals all the time, that is possible, right?

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()

That’s not actually the generally the case once you include some keyword arguments in the call. Recall that function types don’t have keyword argument labels any longer, so:

  foo.bar(x: 42)

Is not equivalent to:

  let fn = foo.bar
  fn(x: 42)

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

It completely depends on the direction of the DynamicCallable proposal:
https://gist.github.com/lattner/a6257f425f55fe39fd6ac7a2354d693d

In my original proposal, I had no hook for “DynamicCallableWithKeywordsToo”. Feedback from the list was that people care about smalltalk derived languages (Ruby, Squeak, and yes, ObjC/Swift) and so I added this as a generalization.

That said, I’m not attached to it at all. I can see the advantages of supporting these forms, but I can also see the concern that you’re observing.

I think that proponents for “DynamicCallableWithKeywordsToo” would say that Smalltalky languages (including Swift per the example above) do not provide the ability to separate out the base name, and those a bridge to those language can justifiably eject this concern. In contrast, since Python *does* allow the substitution that you’re suggesting, its bridge absolutely would provide that capability.

In the end, it comes down to the cost benefit tradeoff we’ll have to weigh: how much generality and power is needed and can be justified?

-Chris

···

On Nov 28, 2017, at 8:57 PM, Slava Pestov <spestov@apple.com> wrote:

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

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.

As noted at the end of my message, I think one can write a Swift library to make working with the Python runtime much easier, and write a wrapper generator that produces Swift APIs from Python code that use said Swift library.

That might be true for Python; I’m quite certain it’s not the case for Ruby or JS as they exist in the wild. However…

At the risk of stating a universally unpopular opinion, beyond interop with dynamic languages such as Python, there’s also value in dynamic member lookup for •purely Swift-centric• reasons.

I say this with mixed feelings. I share Douglas’s concerns about breaking Swift’s safety bubble. I like it that the language generally makes it clear •at the point of use• which language features have runtime pitfalls that the compiler cannot check. The points where this is not true of the language (e.g. array subscripts) always feel a bit gritty between the teeth.

There can, however, be great value in constructing type membership at runtime. The ergonomic access to JSON that’s been discussed in this proposal is nothing to sneeze at. Dynamic languages make for lovely DSLs in a way that fully statically type checked languages never do. Things like Rails’s programmatically generated named routes (e.g. “user_post_path(id)” because routing configuration created a user resource with nested posts) are possible to approximate with statically typed approaches — but only clumsily, never with the same expressiveness and concision.

I don’t mean to sneeze at the downside of dynamic typing either. They too are nothing to sneeze at. I’ve spent many years on both sides of the dynamic divide, and all I see is difficult tradeoffs.

Swift so far has said that dynamism isn’t worth the tradeoffs; it’s aggressively avoided even robust runtime reflection, much less dynamic dispatch. That may indeed be the right call for the language — but I’m not easily convinced there isn’t room to open the door wider.

I think of Chris’s post back in the early days of this list about the “programmer model” the language creates, and wonder if there’s a way we can’t make a model that leaves the door open to this kind of dynamic dispatch where it makes sense for the people and situation at hand, while still staying true to the language’s spirit of “compile-time safety by default, exceptions to that by intent at point of use.”

Opening the door to dynamism might take some of the flavor of the way the language currently handles unsafe memory access: it’s possible and even reasonably ergonomic, but you always know when you’ve crossed the Rubicon into Dangerland.

I would really like to see Swift gain more dynamic capabilities eventually. I wrote a lot of Ruby years ago and really enjoyed some aspects of it.

On the other hand, I do want to see us be very deliberate about adding them and do it in the right way and only when we are confident that we have identified the right feature(s) and designs for them. If that means waiting a while I would rather wait than have regrets later. If we can identify a design that feels appropriate for Swift and decide now is the time to introduce it I will be happy with that, but the timeline shouldn’t be prioritized higher than getting the design right.

The absolute minimum criteria I have for something I would consider worth taking a close look at is making dynamic member lookup clear at the call site as Zach Wolfe suggested earlier. If you have other ideas about how to make it clear when you’ve “crossed the Rubicon into Dangerland” I am interested in hearing about them.

I am not sure whether I would be convinced that a design like this is the right thing to do or not but am willing to give it deeper consideration. I would also like to see the direction Doug alluded to explored in detail. The alternatives need to be in explored enough depth that we can evaluate their relative strengths and weaknesses. That hasn’t happened yet.

···

On Nov 30, 2017, at 3:48 PM, Paul Cantrell via swift-evolution <swift-evolution@swift.org> wrote:

On Nov 30, 2017, at 3:40 PM, Douglas Gregor via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Nov 30, 2017, at 1:01 PM, Zach Wolfe <zacharyreidwolfe@gmail.com <mailto:zacharyreidwolfe@gmail.com>> wrote:

Cheers,

Paul

  - Doug

On Nov 30, 2017, at 2:24 AM, Douglas Gregor via swift-evolution <swift-evolution@swift.org <mailto: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”.

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.

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 <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

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

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

Can you explain in what sense AnyObject lookup is type unsafe where your proposal is type safe? Both seem type safe in the sense that your program will not hit undefined behavior at runtime and instead trap if you access a non-existent member; both are type unsafe in the sense that you cannot reason statically about whether any specific member lookup will succeed. What is the distinguishing characteristic between the two that makes one safe and the other not?

Slava

···

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

The difference between it and AnyObject lookup is that AnyObject lookup is:

b) type unsafe