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

The semantics of the DLR seems fairly similar to Chris’ proposal, does it not? Btw, this makes me think that I would prefer to have one proposal contain both dynamic member lookup and dynamic method calls: it would be kind of silly to have one accepted and not the other.

David.

···

On 1 Dec 2017, at 17:38, C. Keith Ray via swift-evolution <swift-evolution@swift.org> wrote:

Should we compare dynamic member lookup to Microsoft's DLR ?

Dynamic Language Runtime Overview - .NET Framework | Microsoft Learn

Quote:
The dynamic language runtime (DLR) is a runtime environment that adds a set of services for dynamic languages to the common language runtime (CLR). The DLR makes it easier to develop dynamic languages to run on the .NET Framework and to add dynamic features to statically typed languages.
Dynamic languages can identify the type of an object at run time, whereas in statically typed languages such as C# and Visual Basic (when you use Option Explicit On) you must specify object types at design time. Examples of dynamic languages are Lisp, Smalltalk, JavaScript, PHP, Ruby, Python, ColdFusion, Lua, Cobra, and Groovy.
Most dynamic languages provide the following advantages for developers:
The ability to use a rapid feedback loop (REPL, or read-evaluate-print loop). This lets you enter several statements and immediately execute them to see the results.
Support for both top-down development and more traditional bottom-up development. For example, when you use a top-down approach, you can call functions that are not yet implemented and then add underlying implementations when you need them.
Easier refactoring and code modifications, because you do not have to change static type declarations throughout the code.
Dynamic languages make excellent scripting languages. Customers can easily extend applications created by using dynamic languages with new commands and functionality. Dynamic languages are also frequently used for creating Web sites and test harnesses, maintaining server farms, developing various utilities, and performing data transformations. + <>
The purpose of the DLR is to enable a system of dynamic languages to run on the .NET Framework and give them .NET interoperability. The DLR introduces dynamic objects to C# and Visual Basic in Visual Studio 2010 to support dynamic behavior in these languages and enable their interoperation with dynamic languages.

--
C. Keith Ray

* What Every Programmer Needs To… by C. Keith Ray [PDF/iPad/Kindle] <- buy my book?
* http://www.thirdfoundationsw.com/keith_ray_resume_2014_long.pdf
* http://agilesolutionspace.blogspot.com/

On Dec 1, 2017, at 8:07 AM, Benjamin G via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Fri, Dec 1, 2017 at 4:35 PM, Jon Gilbert via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
> On Nov 30, 2017, at 08:10, Karl Wagner via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.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.

As a sidenote, the weakest points of python on the server side is compile-time type safety (none) and concurrency (almost none either). If we want to convince anyone at the moment to switch to swift on the backend, i'd say the urgent part isn't to make one or two function calls to a python library more "swifty" (but without any guarantee that we didn't mistype a function name). I'd say we should better focus on having a great agent concurrency model, as well as even better generics and faster compile time.
From what i've observed around me, people seem to migrate python code to golang. Not ruby or lisp.

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

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.

I may have spoken incorrectly but I do still believe it is contrary to what I (and apparently many others) feel is the spirit of Swift.

As I understand it, AnyObject lookup was a necessity during the historical development of Swift. The attempts to narrow its scope have merit regardless of how far they can go. They indicate that perhaps it is a feature we would remove if that were possible. I would love to see that happen someday (although I know it is unlikely), or at least see it be made explicit at the call site. I suspect if this feature did not exist and it was proposed today it would be met with at least as much resistance as your proposals have.

This isn’t the way I see it. You’re right that we want to reduce AnyObject dispatch wherever possible: types are good after all. Swift on Linux doesn’t even have AnyObject dispatch, so you can tell that it is a means to an end, not a feature that we think is great for Swift on its own merits.

What is that end? It is interoperability with truly dynamic values, which do occur in Objective-C, where pervasive casting would harm code clarity and usability. The desire to fix AnyObject you are seeing are related to two different issues: 1) The design of AnyObject dispatch itself is problematic in some ways, and 2) many values in ObjC APIs were typed as id simply because there was no way to describe a more specific type given Objective-C’s original type system (which has been improved). However, given the presence of actually polymorphic values being used by some APIs, I don’t imagine it going away entirely.

In the case of other dynamic languages, the situation is even more severe. Some dynamic languages have progressive type systems available, but not all do. Those which do have to deal with the fact that the base languages were not designed for typing systems, which is far more severe a problem than Objective-C’s situation.

If you haven’t already, I encourage you to read the mypy docs (http://mypy.readthedocs.io/en/stable/index.html\) to understand how the type system they’re trying to build on top of Python works. They have to contend with things like:

- the single array subscript operator taking both scalars and ranges, and returning different type results depending on their input (Python has no overloading)
- pervasive use of duck typing, e.g. int is duck type compatible with float and complex (duck typing is for many non-primitive types as well)
- pervasive use of variance
- APIs creep to “just work” when passed all sorts of weird values, meaning that their API contract is extremely loose.
- Python supports *dynamically computed base classes* and lots of other crazy things.
- and more..

Mypy also only supports a subset of Python - it doesn’t support properties with setters as one example, and its generic and class system is only partially implemented. The only way to use it in practice is to use their ability to silence warnings, it is not an acceptable basis for a sound type system that we could depend on in Swift.

For all those reasons, we really do need something like AnyObject dispatch if we care about working with dynamically typed languages. The design I’m suggesting carefully cordons this off into its own struct type, so it doesn’t infect the rest of the type system, and is non-invasive in the compiler.

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.

The primary problem I have is that misuse is too easy and too subtle. It is at least as easy to misuse as other features in Swift which have been carefully designed to be obvious at usage sites.

I was happy to see that you are willing to consider Xiaodi’s suggestion to require conformance to be stated as part of the original type declaration. That would be a significant improvement to the proposal IMO.

I’ve revised the proposal to require this.

I strongly urge you to reconsider the decision of that dynamic members must be made available with no indication at usage sites. An indication of dynamic lookup at usage sites aligns very well (IMO) with the rest of Swift (AnyObject lookup aside) by calling attention to code that requires extra care to get right.

I don’t understand this. The proposal is fully type safe, and this approach is completely precedented by AnyObject. Swift’s type system supports many ways to express fallibility, and keeping those decisions orthogonal to this proposal is the right thing to do, because it allows the author of the type to decide what model makes sense for them.

In any case, I hear loud and clear that there is concern about possible abuse of this proposal, so I added a section in the alternatives section to discuss further ways to reduce possibility for abuse:
https://gist.github.com/lattner/b016e1cf86c43732c8d82f90e5ae5438#reducing-potential-abuse

More suggestions are welcome.

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.

I’ll mostly let Doug continue the discussion of the approach he is suggesting. It looks to me like that is an approach that might help in this area. For example, if a symbol is removed it may be possible to produce a compiler error in at least some cases. I don’t think Doug’s suggestion precludes full dynamism but would offer a much better experience in at least some cases. Perhaps it would even be enough to call into question whether full dynamism is really necessary or not (as Doug seems to think). We won’t know for sure without exploring this further.

I understand that you believe the direction he has advocated is not realistic. Perhaps, but I do appreciate that the conversation is happening before a final decision is made. I support further exploration of this at least as long as Doug believes it is a good idea to do so.

Yes, I agree, it is important to get aligned on this.

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.

One of the stated aims of this proposal is to bring many new programmers into the Swift community. That is a wonderful goal!

Of course that may include programmers with a different set of technical values than are currently predominant in the Swift community. If we make it really easy to use Swift as a highly dynamic language we may soon see a large sub-community form that uses Swift in this way. Regardless of how you would feel about this I think it’s an important possible outcome to consider.

You mentioned that many of them may prefer proper Swift APIs for the libraries they use and are hoping that the issues with dynamism will incentive them to create these in time. Perhaps requiring some kind of call-site annotation would increase this incentive without being significant enough to disincentive using these libraries in Swift.

I think you over-estimate how awesome Python (and other language APIs) are going to be, even with these two proposals. Keep in mind that Python has the GIL for example, the naming conventions and design patterns are completely different, etc. Even with both of these proposals, there will be high incentive for building pure Swift APIs to replace things over time. The situation is similar to C interop with Swift: it is incredibly important that it exist, but people aren’t exactly aiming to use UnsafePointer for everything :-). It is possible someone would do so, but people do all sorts of bad things locally in their codebase. We cannot prevent that.

In fact, I would argue that making Python interoperability *too good* is the bigger risk we have. If there is no reason for people to move off of using those APIs, they will continue to do so forever. That isn’t a transition path, that’s a merger of communities.

In particular, if you continue to insist on standard dot syntax for member lookup I would appreciate hearing more about why that is a sticking point for you, especially in light of your recent comment about people looking to move away from Python.

Thanks, I addressed this in the alternative section.

-Chris

···

On Dec 1, 2017, at 5:50 PM, Matthew Johnson <matthew@anandabits.com> wrote:

Correct. The argument also fails to recognize that (when bridging to a dynamic language):

  x+y

Is a completely dynamic method call which can fail (or return IUO), as is:

  x[i]

And that this is true with no changes to Swift. The claim that such a thing is counter to the design of Swift is completely perplexing to me.

-Chris

···

On Dec 3, 2017, at 11:03 AM, Magnus Ahltorp <map@kth.se> wrote:

4 Dec. 2017 02:40 Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:

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.

This example shows what many on this list don't believe: that any Swift method or member access can fail. If the return value of this "get" method is an IUO, or not an Optional at all, and doesn't throw, then the expression would have to fail hard if "foo" didn't resolve to something meaningful.

The most common argument against this proposal is that someone could make an API using Dynamic Member Lookup that could fail even though it is not apparent to the caller. But, as we see in the example, this is just as possible today.

Member lookup is already entirely API author defined. You can define computed properties that do anything on get and set. There is nothing fundamentally new here at all.

-Chris

···

On Dec 3, 2017, at 3:47 PM, Jonathan Hull <jhull@gbis.com> wrote:

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

The behavior is different though (the invisible guard rails which we normally rely on to prevent mistakes are missing), so it should look similar without being identical.

Chris, having myself nursed this sense that Swift is somehow “especially safe” in a way that feels closer to ML than C, you’ve prompted me to consider _why_ I feel that way.

Sentiments like this subjectively fit the _experience_ of using Swift. When using the language in practice, it’s fairly clear to a mindful developer which operations carry some inherent level of danger, what kind of danger that is, how to reason about it, and how to mitigate it. (Usually, anyway.)

Having all had this pleasant experience, I think we may be too quick to look for the explanation in the design of the language itself. We fail to notice that it comes not from the language alone, but from the whole ecosystem that surrounds it: standard library design, naming conventions, tools, libraries, culture, Erica’s books, etc.

Optionals present themselves as a specially privileged part of the language; `withUnsafeBytes` presents itself as a library call using existing language features. But both share a similar definitively Swift-ish aesthetic in how they guide our attention, and how they circumscribe programmer error while leaving the language ergonomic and situationally adaptable.

Your reminder is a good one: Swift already does not have, and never has had, a sense of safety that depends on Haskell-like strictness in the language itself. The design question at hand in this thread thus becomes not “how can we prevent dynamic dispatch from ruining everything” but rather “how can we introduce this new element in a way that does not disrupt the ecosystem.” In considering that, we’d do well to remember the long-established language features that have so far failed to ruin everything despite their impurity.

• • •

On this thread, there’s a lot of the tired old reflexive sneering at dynamic languages — I’ll spare my thoughts on that here — and a fear that creeping dynamism will ruin the whole neighborhood and destroy all we hold dear. I don’t think that will happen. I see Chris’s other examples, and the line of thought above, as an optimistic answer to that fear: Swift’s Swiftiness has _always_ depended on good judgement. It has always depended on an ecosystem that extends beyond the language features, and in some particular cases even pushes toward mindfulness and safety _in spite of_ language features. A language feature alone will not erase the good sense of the language’s stewards, or poison its community.

I think — incurring universal flaming in this discussion, I’m sure — there is a place for dynamic dispatch in Swift. There are a few problems, like the Python interop, where it is the right answer. And I’m sure that the ecosystem can integrate it in a way that does not fundamentally alter what we like about Swift. This is a language that’s managed to integrate **raw memory access**, for heaven’s sake, and still “feel closer to ML than C!” If it can do that, it can certainly withstand the far smaller disturbance in the force of what Chris is proposing here.

Cheers,

Paul

···

On Dec 3, 2017, at 1:26 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:

On Dec 3, 2017, at 11:03 AM, Magnus Ahltorp <map@kth.se> wrote:

4 Dec. 2017 02:40 Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:

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.

This example shows what many on this list don't believe: that any Swift method or member access can fail. If the return value of this "get" method is an IUO, or not an Optional at all, and doesn't throw, then the expression would have to fail hard if "foo" didn't resolve to something meaningful.

The most common argument against this proposal is that someone could make an API using Dynamic Member Lookup that could fail even though it is not apparent to the caller. But, as we see in the example, this is just as possible today.

Correct. The argument also fails to recognize that (when bridging to a dynamic language):

  x+y

Is a completely dynamic method call which can fail (or return IUO), as is:

  x[i]

And that this is true with no changes to Swift. The claim that such a thing is counter to the design of Swift is completely perplexing to me.

+1 to Paul's comments

Many of the concerns about typos causing runtime bugs scream out to me, "I want to write code that is completely untested and only validated by the compiler". But this is just as bad for a strong, statically typed language as it is for a weak, dynamically typed language (and honestly, in my experience, dynamic language communities seem to be way better about writing tests due to the fact that statically typed communities tend to rely way more on the compiler). At the very least, I would expect a programmer to run a happy path to make sure the code they wrote actually works. And if they don't, and it isn't then caught by QA, then there are much larger issues at hand than the language design. I'm not sure if people are concerned about the debug cycle or production issues due to these proposed changes. There's simply no way around the fact that writing code for a dynamic programming model is going to result in more time spent verifying, testing, and debugging runtime issues; no matter the amount of effort expended on gathering the maximal amount of type information available from the dynamic language, or the call site symbols the programmer is required to enter to get the code to compile.

···

On Dec 3, 2017, at 8:06 PM, Paul Cantrell via swift-evolution <swift-evolution@swift.org> wrote:

On Dec 3, 2017, at 1:26 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:

On Dec 3, 2017, at 11:03 AM, Magnus Ahltorp <map@kth.se> wrote:

4 Dec. 2017 02:40 Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:

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.

This example shows what many on this list don't believe: that any Swift method or member access can fail. If the return value of this "get" method is an IUO, or not an Optional at all, and doesn't throw, then the expression would have to fail hard if "foo" didn't resolve to something meaningful.

The most common argument against this proposal is that someone could make an API using Dynamic Member Lookup that could fail even though it is not apparent to the caller. But, as we see in the example, this is just as possible today.

Correct. The argument also fails to recognize that (when bridging to a dynamic language):

  x+y

Is a completely dynamic method call which can fail (or return IUO), as is:

  x[i]

And that this is true with no changes to Swift. The claim that such a thing is counter to the design of Swift is completely perplexing to me.

Chris, having myself nursed this sense that Swift is somehow “especially safe” in a way that feels closer to ML than C, you’ve prompted me to consider _why_ I feel that way.

Sentiments like this subjectively fit the _experience_ of using Swift. When using the language in practice, it’s fairly clear to a mindful developer which operations carry some inherent level of danger, what kind of danger that is, how to reason about it, and how to mitigate it. (Usually, anyway.)

Having all had this pleasant experience, I think we may be too quick to look for the explanation in the design of the language itself. We fail to notice that it comes not from the language alone, but from the whole ecosystem that surrounds it: standard library design, naming conventions, tools, libraries, culture, Erica’s books, etc.

Optionals present themselves as a specially privileged part of the language; `withUnsafeBytes` presents itself as a library call using existing language features. But both share a similar definitively Swift-ish aesthetic in how they guide our attention, and how they circumscribe programmer error while leaving the language ergonomic and situationally adaptable.

Your reminder is a good one: Swift already does not have, and never has had, a sense of safety that depends on Haskell-like strictness in the language itself. The design question at hand in this thread thus becomes not “how can we prevent dynamic dispatch from ruining everything” but rather “how can we introduce this new element in a way that does not disrupt the ecosystem.” In considering that, we’d do well to remember the long-established language features that have so far failed to ruin everything despite their impurity.

• • •

On this thread, there’s a lot of the tired old reflexive sneering at dynamic languages — I’ll spare my thoughts on that here — and a fear that creeping dynamism will ruin the whole neighborhood and destroy all we hold dear. I don’t think that will happen. I see Chris’s other examples, and the line of thought above, as an optimistic answer to that fear: Swift’s Swiftiness has _always_ depended on good judgement. It has always depended on an ecosystem that extends beyond the language features, and in some particular cases even pushes toward mindfulness and safety _in spite of_ language features. A language feature alone will not erase the good sense of the language’s stewards, or poison its community.

I think — incurring universal flaming in this discussion, I’m sure — there is a place for dynamic dispatch in Swift. There are a few problems, like the Python interop, where it is the right answer. And I’m sure that the ecosystem can integrate it in a way that does not fundamentally alter what we like about Swift. This is a language that’s managed to integrate **raw memory access**, for heaven’s sake, and still “feel closer to ML than C!” If it can do that, it can certainly withstand the far smaller disturbance in the force of what Chris is proposing here.

Cheers,

Paul

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

>
>> 4 Dec. 2017 02:40 Chris Lattner via swift-evolution <
>>
>> 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/b016e1cf86c43732c8d82f90e5ae54
38#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.
>
> This example shows what many on this list don't believe: that any Swift
method or member access can fail. If the return value of this "get" method
is an IUO, or not an Optional at all, and doesn't throw, then the
expression would have to fail hard if "foo" didn't resolve to something
meaningful.
>
> The most common argument against this proposal is that someone could
make an API using Dynamic Member Lookup that could fail even though it is
not apparent to the caller. But, as we see in the example, this is just as
possible today.

Correct. The argument also fails to recognize that (when bridging to a
dynamic language):

        x+y

Is a completely dynamic method call which can fail (or return IUO), as is:

        x[i]

And that this is true with no changes to Swift. The claim that such a
thing is counter to the design of Swift is completely perplexing to me.

I think the argument basically is "let's not add another footgun" (because
the design of Swift , for example regarding null handling, is to have less
footguns than other languages). The fact that there are footguns in swift
isn't an argument for adding new ones.

···

On Sun, Dec 3, 2017 at 8:26 PM, Chris Lattner via swift-evolution < swift-evolution@swift.org> wrote:

On Dec 3, 2017, at 11:03 AM, Magnus Ahltorp <map@kth.se> wrote:
swift-evolution@swift.org> wrote:

-Chris

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

I think the argument basically is "let's not add another footgun" (because
the design of Swift , for example regarding null handling, is to have less
footguns than other languages). The fact that there are footguns in swift
isn't an argument for adding new ones.

Couldn’t have said it better. This is what it all boils down to.

···

On Mon, Dec 4, 2017 at 12:16 AM, Benjamin G via swift-evolution < swift-evolution@swift.org> wrote:

On Sun, Dec 3, 2017 at 8:26 PM, Chris Lattner via swift-evolution < > swift-evolution@swift.org> wrote:

On Dec 3, 2017, at 11:03 AM, Magnus Ahltorp <map@kth.se> wrote:
>
>> 4 Dec. 2017 02:40 Chris Lattner via swift-evolution <
swift-evolution@swift.org> wrote:
>>
>> 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/b016e1cf86c43732c8d82f90e5ae
5438#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.
>
> This example shows what many on this list don't believe: that any Swift
method or member access can fail. If the return value of this "get" method
is an IUO, or not an Optional at all, and doesn't throw, then the
expression would have to fail hard if "foo" didn't resolve to something
meaningful.
>
> The most common argument against this proposal is that someone could
make an API using Dynamic Member Lookup that could fail even though it is
not apparent to the caller. But, as we see in the example, this is just as
possible today.

Correct. The argument also fails to recognize that (when bridging to a
dynamic language):

        x+y

Is a completely dynamic method call which can fail (or return IUO), as is:

        x[i]

And that this is true with no changes to Swift. The claim that such a
thing is counter to the design of Swift is completely perplexing to me.

I think the argument basically is "let's not add another footgun" (because
the design of Swift , for example regarding null handling, is to have less
footguns than other languages). The fact that there are footguns in swift
isn't an argument for adding new ones.

-Chris

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

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

I just wanted to add that the single purpose of a static type system is to ensure that the methods being called on a receiver are present at runtime and take arguments of the types known at compile time. Of course the type system does not guarantee that those calls to not fail. Even in Haskell there is no guarantee that a function call does not fail as it allows partial patterns (therefore `head ` crashes). Or a function call might enter an infinite loop. But, to repeat, the type system guarantees that the method or member itself will be present.

Therefore I cannot follow the argument that it follows from the possible failure of a Swift method or member access that it is ok or even a comparable type of failure if the method or member itself is not present. This would imply that Swift’s static type system is unnecessary. Why check for the presence of a method or member if calling it or accessing it might fail anyway? No, I do not buy that argument. And I do not think that I have to provide examples where a static type system is of help.

The remaining question therefore is whether it is ok to remove the static type system for certain isolated use cases and that might be the case. But I like others would prefer if those use cases would be isolated somehow visually. Someone proposed to require `dynamic` before expressions containing dynamic member lookups similar to `try`, with the additional option to enclose a whole block into `dynamic { … }` to express the same as prefixing each expression with `dynamic`. I still think this has merit:

let result = dynamic x.foo.bar  // will crash if foo or bar are not present

let result = dynamic? x.foo.bar // will return nil if foo or bar are not present

// will crash if foo or bar are not present
let result = dynamic {
	let y = x.foo.bar
	let z = y.baz(42)
	return z.zork()
}

// will return nil if foo or bar are not present
let result = dynamic? {
	let y = x.foo.bar
	let z = y.baz(42)
	return z.zork()
} 

It allows to clearly demarcate the border between regions which are fully statically type checked and those where the static type system be only partially available (depending on the types of the respective receivers) while not being very intrusive due to the block notation.
Of course I can still have a mixture of static and dynamic member accesses _within_ a dynamic block but at least I know that when I am outside of such a block there won’t be any dynamic member accesses. None. Zero.

-Thorsten

···

Am 03.12.2017 um 20:04 schrieb Magnus Ahltorp via swift-evolution <swift-evolution@swift.org>:

4 Dec. 2017 02:40 Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:

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.

This example shows what many on this list don't believe: that any Swift method or member access can fail. If the return value of this "get" method is an IUO, or not an Optional at all, and doesn't throw, then the expression would have to fail hard if "foo" didn't resolve to something meaningful.

The most common argument against this proposal is that someone could make an API using Dynamic Member Lookup that could fail even though it is not apparent to the caller. But, as we see in the example, this is just as possible today.

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.

After thinking about your email a bit more, I think I might understand the disconnect we’re having. I think we have different versions in mind of what “success” looks like:

I believe your view is that people start using Python APIs from their Swift code, but gradually add type annotations (either inline or in a sidecar database) to make those APIs progressively more Swifty. New features will be added to Python (its type system, compilers, databases for common APIs, etc) to improve this interoperability, along the lines of what we’ve done for Objective-C over the years. Fast forward several years, and large Python libraries would be nice to use from Swift - perhaps nicer than they are to use from Python itself. This view aligns with what happened with Objective-C <-> Swift interoperability.

In contrast, I’m specifically interested in developers in certain large domains (e.g. data science and ML) which are “forced” to use Python because that is where all the libraries are. I have spoken to many of these sorts of people, and a large number of them really *dislike* using Python for all the obvious reasons (including the tooling issues you point out). My view of success is that we allow them to write all of *their code* in Swift, which will lead to a massive quality of life benefit for these frustrated people. You’re right that they will still chaff when using imported Python APIs (which don’t feel “swifty” for LOTS of reasons - e.g. method naming and design patterns), but my view is that this will provide incentive for these people to provide a proper Swift implementation for these libraries over time.

In short, your end game is a pervasively intertwined Swift/Python world (like ObjC and Swift are). My view is that Swift holds Python at arm's length, and wins over the hearts and minds of developers, leading to new Swift APIs designed for Swift.

That’s not the end game I’m looking at. I’d rather make it feasible to generate Swift wrappers for Python libraries that fit well into Swift’s existing machinery. The complexity goes outside the compiler, with small extralinguistic tie-ins to make it work well.

I’m not sure if you agree with that portrayal of your position, and if not, I’m sorry and will try to understand another way. However, if I’m close, then I have several concerns about that vision and don’t believe the end-game is achievable or better than what I’m proposing. Consider:

- I don’t think there will be a lot of success getting people who *actually love* Python to use Swift, unless there is already an extrinsic reason for them to use it.
- Type hints are not widely used in Python, and there are believable reasons that they won’t ever be. Because they are specifically poorly suited for libraries, their use seems to be in “user’s own code” - but my proposal solves this already! See below for examples.
- Even if type hints were widely adopted, it would require massive extensions to them and to Python to make them be "good enough” to provide value for the things you’re envisioning. Analogs to instancetype, objc generics, lots of the C macros, and many of the other things we’ve added to ObjC would have to be added.
- The Python community has no reason to do this work for the Swift community, or accept changes to Python that are required to make this actually great.
- Beyond the core type system, the Python and Objective-C languages work extremely differently in other ways (e.g. lack of umbrella headers making AnyObject-style dispatch questionable).
- Even with those annotations and all the work, the APIs we’d end up with are not going to be good Swift APIs. I don’t think that “renamification” and "IUO audits" would ever actually happen in practice, for example.
- I believe the engineering effort required to implement this vision is so massive (including the changes to Swift, Python, and Python libraries) that it simply will never actually happen.

They don’t have to be good, strictly-typed APIs to be useful. They should have method/property names, have documentation, and be associated with named classes to work well with existing tooling.

More concerning to me is that your design of using the existing AnyObject type presents a really concerning technical problem, scalability: It is not simply a Swift/ObjC/Python world, Javascript is also super important. There are also a number of other less-widely used dynamic languages that are interesting. Your design leads to them all being mashed together into a common runtime system. I cannot imagine how this would end up being a good thing for system complexity, and would lead to each of them being jeopardized in different ways. As I have mentioned before numerous times, it also opens the door for enormous Swift compiler complexity, as each of the object models need to be supported in the compiler (so you can subclass each languages’ types), and many other complexities.

The AnyObject model of dispatching to any method within a given dynamic space (whether it’s Objective-C or Python or something else) fits well into Swift. You’re absolutely right that we can’t make Python objects work with AnyObject.

If you are serious about improving the tooling situation for people using Python APIs in Swift, the most natural way to do so is to follow the approach that the MyPy community (they are the ones who have thought about this the most) is using: provide progressive typing extensions, use data flow analysis to propagate around types, and enhance the tooling to have support this. I’m skeptical that this will ever be worthwhile, but a sketch could look like this:

I doubt this would be worthwhile. If we wanted to do something along these lines, we’d want to seriously investigate something akin to gradual typing.

Similarly, there is no reason to do anything to make the second and third examples work nicely in Swift: you’d just define a Swift class, and a Swift function.

MyPy:

class BankAccount:
    def __init__(self, initial_balance: int = 0) -> None:
        self.balance = initial_balance
    def deposit(self, amount: int) -> None:
        self.balance += amount
    def withdraw(self, amount: int) -> None:
        self.balance -= amount
    def overdrawn(self) -> bool:
        return self.balance < 0

my_account = BankAccount(15)
my_account.withdraw(5)
print(my_account.balance)
Swift:

class BankAccount {
   var balance : Int
   init(initialBalance: Int) { … }
  func deposit(….
}

Even the proposals that I’m making - simple and isolated though they are - are enough to provide a major quality of life improvement for people writing large amounts of code against Python APIs.

They’re an improvement to the resulting code (i.e., the Swift code looks comparable to the Python code), but it’s not a quality-of-life improvement for the process of writing the code (or understanding it once it’s written), because you don’t get any of the tooling benefits you’re used to having as a Swift programmer: no code completion, no “”quick help”, no go-to-definition.

Beyond that, they are small extensions with low complexity, scale to supporting many different dynamic languages over time, require a level of engineering effort that is plausible to be built, and do not require some sort of "executive buy in” from the Python community.

Here’s an alternative proposal that provides a better development experience: write a wrapper generator, in Python, that maps a Python module’s interfaces to Swift, and provide small tie-ins to the compiler to make it fit well. Take the example from the DynamicMemberLookup proposal:

class Dog:
    """Man's best friend"""
    def __init__(self, name):
        """Summon a new dog"""
        self.name = name
        self.tricks = # creates a new empty list for each dog

    def add_trick(self, trick):
        """Teach the old dog a new trick"""
        self.tricks.append(trick)

The output of the wrapper generator is:

/// Man's best friend
@_foreign(python)
class Dog : PythonObject {
  /// Summon a new dog
  @_foreign(python, “__init__”)
  init(_ name: PythonArgument) { /* code to dynamically call __init__ on the Dog class object*/ }

  /// Teach the old dog a new trick
  @_foreign(python, “add_trick”)
  func add_trick(_ trick: PythonArgument) -> PythonObject? { /* code to dynamically call add_trick */ }
}

Several pieces above require explanation, but first the code to use this from Swift:

import DogModule
let dog = Dog("Brianna")
dog.add_trick("Roll over")
log do2 = Dog("Kaylee").add_trick("snore")

Now, because we have declarations in the Swift module, the tools helped us write this code: I got code completion for the “Dog” type, its initializers, its “add_trick” method. The Python docstrings were carried through into documentation comments that show up in my IDE. I can inspect the Swift interface created by the wrapper generator to see what it looks like; my class browser can show me the Python class hierarchy.

Let’s break down the pieces we need to get here:

PythonObject: this would be a root class, defined by the Python interoperability library:

@_foreign(python)
class PythonObject {
  static func retain(_ object: UnsafePointer<Void>) { Py_INCREF(object) }
  static func release(_ object: UnsafePointer<Void>) {Py_DECREF(object) }
}

PythonObject is a foreign class for the Python language (hence the @_foreign). So while you get subtyping as normal (Dog is a subtype of PythonObject), they aren’t a subtype of AnyObject because it’s not a native object model. Nor could you mix a foreign class marked as @_foreign(python) with one marked as @_foreign(ruby), but they’re different object models.

The retain/release operations would be called by the compiler in the obvious places. It shouldn’t be too difficult to build an appropriate value witness table to make this work.

PythonArgument: This is a protocol to which any type can conform to map it into Python, and IIRC you already have this in your design in some form:

protocol PythonArgument {
  func toPythonObject() -> PythonObject
}

extension PythonObject: PythonArgument {
  func toPythonObject() -> PythonObject { return self }
}

extension Int: PythonArgument {
  func toPythonObject() -> PythonObject { … }
}

@_foreign on methods/properties/etc.: This is there to enable AnyObject-style dispatch. Consider the add_trick example:

  @_foreign(python, “add_trick”)
  func add_trick(_ trick: PythonArgument) -> PythonObject { /* code to dynamically call add_trick */ }

The two arguments to @_foreign are the foreign-class-model indicator (python): one gets AnyObject lookup from objects whose type is in that same foreign class space (e.g., PythonObject is also in the “python” foreign class model), and the second string (here, “add_trick”) is the uniquing string that indicates what name would be passed through to the underling runtime. This becomes important when you have overlap at different parts in the class hierarchy, e.g.,

@_foreign(python)
class MagicShow: PythonObject {
  @_foreign(python, “add_trick”)
  func add_trick(_ trick: PythonArgument) -> PythonObject? { … }
}

And then we have something like:

func foo(po: PythonObject) {
  po.add_trick?(“something”) // finds both Dog.add_trick(_:) and MagicShow.add_trick(_:), but since they’re both uniqued by “add_trick” and have the same signature, we can pick one arbitrarily
}

This is a generalization of how AnyObject dispatch already works in the type checker. For Objective-C, AnyObject is the root for the “objc” class model and the Objective-C selector (with + or - prepended) is used as the uniquing key; you can see the code at https://github.com/apple/swift/blob/master/lib/Sema/ConstraintSystem.cpp#L265-L273\.

Factory initializers: unfortunately, to actually make initializers work cleanly, we’ll need something like factory initializers in the language. We should have those anyway, but until we get them one can fake it with top-level functions:

@_foreign(python, “__init__”)
func Dog(_ name: PythonArgument) -> Dog { /* … */ }

“Truly” dynamic calls: Python code does dynamically create new properties, methods, classes, etc. Those won’t get reflected into the generated Swift interface (because the wrapper generator won’t see them when it just imports the module to inspect it). So, there needs to be some string-based interface for such dynamic calls, which would be provided by the interoperability library in whatever syntax makes it clear what’s happening:

extension PythonObject {
  subscript(property name: String) -> PythonObject? { get { /* … */ } set { /* … */ }
  func call(_ methodName: String, _ args: PythonArgument…, keywordArgs: [String: PythonArgument] = [:]) -> PythonObject? { … }
}

This enables the fully-dynamic behavior, but makes it clear when we’ve conjured up the name as a string.

On-demand wrapper generation: you’ve mentioned earlier in this thread that it’s important to support the playgrounds use case, where one can import whatever Python modules are available to play with them. I *personally* don’t think running a one-off wrapper script once per module is all that onerous, but if it is, we could extend the Swift compiler with a very light plugin mechanism where the plugin mechanism has two operations:

1) Enumerate the names of modules that it can translate (e.g., find all of the Python modules in the Python path and current working directory), and
2) Given a module name, produce the Swift wrapper code for the Swift compiler to then compile (and cache)

Overall, it’s probably more work than DynamicMemberLookup, but it provides a more friendly development experience because the entities in the Python module show throw in Swift—they’re documented, inspectable, code-completable, etc. We get the class hierarchy reflected into Swift so we get proper subtyping behavior. And writing these wrapper generators is something that is easily hackable by anyone who knows the languages involved, it’s easy to test and improve over time.

  - Doug

···

On Dec 1, 2017, at 9:37 AM, Chris Lattner <clattner@nondot.org> wrote:
On Dec 1, 2017, at 12:26 AM, Douglas Gregor <dgregor@apple.com <mailto:dgregor@apple.com>> wrote:

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

Ok, this statement isn’t factually true: I’ll admit that there are two related things to this: the GNU ObjC runtime does track some method types, and ObjC does have @encode strings, which encode a subset of the ObjC type system.

While both are true, neither are used by Swift AnyObject lookup, so the rest of the points still hold.

-Chris

···

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

The Objective-C runtime does not track the type of members or methods.

I think the fear most of us , poor developers working with developers of
various skills, is the potential for abuse. I've heard many times that it
isn't a guiding principle for swift, so i'll just reformulate the concern
in a more acceptable way :
Could your proposal explain how the swift language will still keep
encouraging developers to use static / compile-time type checking *when
it's possible* ? (because i think we can all argue that a compile-time
checks is better than a runtime one). Obviously, python interop isn't my
concern here. I'm talking about the impact of your proposal on pure swift
code.

As example, calling some feature "unsafe", or "force", makes every
developer pause and wonder if there isn't a better alternative. I'd like to
see the same kind of things for dynamic calls.

A few examples of the kind of things that i would think about :
- use different file names for code using dynamic features.
- have every dynamic code included in a dynamic{} block
- have a different syntax for dynamic function calls, using ! or !!!
- Make the dynamic calls work only of subclasses of a base "PythonObject".
That would make the goal clear.
etc.
Now, i think those are all bad ideas (and that's why i don't like the
proposal in the first place). But i really think the concern should be
addressed.

···

On Fri, Dec 1, 2017 at 11:03 AM, Benjamin G via swift-evolution < swift-evolution@swift.org> wrote:

On Fri, Dec 1, 2017 at 10:30 AM, Chris Lattner via swift-evolution < > swift-evolution@swift.org> wrote:

On Dec 1, 2017, at 12:26 AM, Douglas Gregor <dgregor@apple.com> wrote:

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

Perhaps it depends on what you mean by “strong”: I interpreted that as
meaning that it provides type safety, with a level of strength akin to what
Java provides: a level that could support mobile code deployment.

Swift certainly does not provide that strong of a static type system,
because it gives people explicit ways to opt out of that.
UnsafeMutableRawPointer, unsafe bitcast, and many other facilities support
this. It also allows calling into non-type safe code, so it isn’t very
strong that way. There are also race conditions and other holes in the
type system.

All that said, I think it is correct that a subset of Swift exists that
does provide strong type safety, but particularly when bridging to C/ObjC
is involved, that quickly goes away.

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.

You’re suggesting that the transitive closure of all Python methods and
properties be preprocessed into a single gigantic Swift PyVal type? I
guess something like that could be done.

I would be concerned because there are many N^2 or worse algorithms in
the Swift compiler would probably explode. It also doesn’t provide the
great tooling experience that you’re seeking, given that code completion
would show everything in the Python universe, which is not helpful.

Further, it doesn’t provide a *better* experience than what I’m
suggesting, it seems strictly worse. A preprocessing step prevents users
from playfully importing random Python modules into the Swift repl and
playgrounds. It also seems worse for implementors (who will get a stream
of new bugs about compiler scalability).

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.

Sure, that’s status quo for Python APIs.

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.

To be clear, I’m not suggesting a change to AnyObject lookup. I don’t
think that it is worth changing at this point in time. My point was to
observe that the proposed DynamicMemberLookupProtocol proposal does not
suffer from these problems. It really is type / memory safe, assuming a
sane implementation.

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.

I’m not opposed to going further over time, but I’d like to get started
at some point :-). I’m not in a super urgent hurry to get this in in the
next week or month or anything like that, but I also don’t want to wait
until Swift 10.

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

I don’t understand your rationale here. I think you agree that we need
to support the fully dynamic case (which is what I’m proposing). That
said, this step does not preclude introducing importer magic (either
through compiler hackery or a theoretical "type providers” style of
feature). Doing so would provide the functionality you’re describing.

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.

Ok, that would be a nice step, but doesn’t fix the type safety hole.

In any case, my proposal allows the use of strong optional results as
well, so the fate of AnyObject isn’t really bound up with 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.

Completely agreed. The major advantage I see of changing it now is if
there is some small mostly-user-invisible-change that allows a dramatic
simplification to the compiler implementation.

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

I understand that, but you’re also accusing the proposal of being a
suboptimal design made by looking at a series of small easy steps, instead
of the right design for the long term. I’m pointing out that it is hard to
see the rationale for that sort of claim.

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.

Agreed. As I mentioned in my previous email, I definitely screwed that
up by not capturing this discussion in the proposal. Thank you again for
pulling this perspective to the front of the discussion so I could fix that
oversight.

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.

Agreed. This is why I’ve been proactive about starting threads and
trying to keep visibility on the proposal each time there is a significant
change. I really do value the discussion and feedback (both on the
proposed direction but also the writing itself).

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.

Ok, but at some point, if there is no alternative proposed, then a strong
opposition has the appearance of saying “we shouldn’t solve this problem”.
It was my understanding that thought that this was a worthwhile problem to
solve.

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

There are many parts to this, which have to do with the ObjC<->Swift
situation being very different than the Python<->Swift situation:

1) The annotations don’t have significant traction in the Python
community.
2) The Python annotations are not as powerful as ObjC generics are, and
thus lack important expressive capability.
3) Many Python APIs are wrappers for C APIs. “Swiftizing” a Python API
in this case means writing a new Swift wrapper for the API, not adding type
annotations.
4) The Python community doesn’t care about Swift, and are not motivated
to do things to make Swift succeed.
5) There is no “clang equivalent” for Python (that I’m aware of) which
close enough to the way Clang does for us to directly use. The owners of
the existing Python compiler/interpreter implementations are not going to
be strongly motivated to change their stuff for us.

Finally, just MHO, but I don’t expect a lot of “mix and match"
Python/Swift apps to exist (where the developer owns both the Python and
the Swift code), which is one case where type annotations are super awesome
in ObjC. IMO, the most important use-case is a Swift program that uses
some Python APIs.

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.

Your points are valid, but the advantages for Objective-C don’t obviously
translate to Swift. Note that ObjC (due to its heritage) has very long
method names that are perhaps arguably designed to not conflict with each
other often. Python doesn’t have this heritage, and it has much shorter
names, which means that we’ll get a lot more conflicts and a lot less
“safety" out of this.

AnyObject lookup also depends on a strange set of scoping heuristics that
was designed to be similar to Clang’s “header import” scope. It isn’t
clear that this approach will work in Python, given that it doesn’t have an
analogue of umbrella headers that import things that cross frameworks.

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.

I didn’t realize that you were thinking we would literally use AnyObject
itself. I haven’t thought fully through it, but I think this will provide
several problems:

1) You’re mushing all of the ObjC and Python world’s together, making the
ObjC interop worse just because you’re doing some Python stuff too.
2) You’re introducing ambiguity: does “ao = [1,2,3]” create an NSArray or
a Python array? How do string literals work? (The answer is obvious,
Python loses). Maybe there is some really complicated bridging solution to
these problems, but that causes its own massive complexity spiral.
3) You can’t realistically overload the Python operator set on AnyObject,
which means you get a worse python experience.
4) AnyObject magic is currently limited to Apple platforms. This would
bring its problems to other platforms like Linux.

There are probably other issues, but I haven’t thought through it.

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.

Fair point, it’s unclear to me how useful this would be with python’s
style of naming, but it could work.

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.

As I mention above, I expect this to expose significant scalability
problems in the Swift compiler and it also defeats REPL/Playgrounds. Being
able to use the Swift REPL is really important for Python programmers.

-Chris

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

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

HI Doug,

Your approach can definitely work, but before responding with a tradeoff analysis, I’d like to make sure that I understand what you’re suggesting specifically.

It also doesn’t provide the great tooling experience that you’re seeking, given that code completion would show everything in the Python universe, which is not helpful.

It’s better for code completion to provide too much than to provide nothing at all. If code completion provides too much, typing a small number of characters will reduce the completion set down to something manageable quite fast.

Yes, I understand your goal.

DynamicMemberLookup doesn’t gave any of those, because “add_trick” is just a string that looks like an identifier. Only the Python runtime can resolve.

Agreed.

A preprocessing step prevents users from playfully importing random Python modules into the Swift repl and playgrounds.

*At worst*, you invoke the tool from the command line to build the Swift module that corresponds to a given Python module.

  py2swift <pythonmodulename>

We could absolutely introduce tooling hooks to make the compiler initiate that step.

This would have to be integrated into the swift compiler to provide a usable experience IMO, because otherwise it won’t work with Playgrounds.

It also seems worse for implementors (who will get a stream of new bugs about compiler scalability).

To my knowledge, AnyObject lookup has not been a serious source of performance problems for the compiler. The global lookup table it requires was annoying to implement, but it’s just a symbol table.

Right, AnyObject lookup itself is not a problem, because significant engineering effort has been put in place to build a module cache that can be efficiently queried. I thought you were suggesting that we do a preprocessing step to smoosh together all of the “interesting” python APIs into a single huge extension on PyVal which would then be literally parsed and that overload resolution would resolve against. This approach would perform very differently than AnyObject lookup does for ObjC.

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.

I don’t understand your rationale here. I think you agree that we need to support the fully dynamic case (which is what I’m proposing). That said, this step does not preclude introducing importer magic (either through compiler hackery or a theoretical "type providers” style of feature). Doing so would provide the functionality you’re describing.

I don’t agree that we need language support for the fully-dynamic case. There has to be a way to handle the fully-dynamic case, but it can be string-based APIs vended by an Python interoperability library.

I believe that the majority of property and method accesses in Python programs will be resolved to a definition in the current model or an imported module, i.e., a definition that is visible to the compiler at compile-time. There are exceptional cases involving programmatically creating properties from JSON, a database, etc., but while prominent I suspect they account for a relatively small fraction of use sites. Do you agree with this statement?

Without pervasive type annotations I’m not sure what you mean. Can you elaborate more? Even if foo is typed, “foo.bar.baz()” won’t be in general because Python does not have typed property declarations.

Finally, just MHO, but I don’t expect a lot of “mix and match" Python/Swift apps to exist (where the developer owns both the Python and the Swift code), which is one case where type annotations are super awesome in ObjC. IMO, the most important use-case is a Swift program that uses some Python APIs.

Let’s consider Python type annotations to be useless for our purposes. None of my argument hinges on it.

Good. I’ll ignore them for the moment.

I didn’t realize that you were thinking we would literally use AnyObject itself. I haven’t thought fully through it, but I think this will provide several problems:

You are correct that literally using AnyObject has these issues. There appears to be a lot of confusion about what the AnyObject model *is*, so let’s sort through that. I think it is reasonable to take the AnyObject model and replicate it for PythonObject.

Ok, good, using literally AnyObject is super problematic in my opinion.

So your implementation approach suggestion is to:

1) Define a new builtin type PythonObject. Is this a base class, a new nominal type, or something else? I assume a base class.
2) It supports AnyObject-style lookup, so code completion works.
3) The database of methods is built from the Python modules.
4) PythonObject is callable, dynamic member lookupable, subscriptable, and has implicit conversions to make it work well.
5) Python derived classes are modeled as Swift classes that derive from PythonObject.

Am I missing something? If this is the approach you’re suggesting, I’ll explain the pros and cons vs my approach.

As I mention above, I expect this to expose significant scalability problems in the Swift compiler and it also defeats REPL/Playgrounds. Being able to use the Swift REPL is really important for Python programmers.

I’ve addressed both of these issues above. The approach I am proposing requires no language or compiler support, works with existing Swift tooling, fits with an existing notion in the language (AnyObject), and can be implemented via a Python script.

I’m very confused, are you suggesting a preprocessor, or something actually integrated with the compiler? The goal here is to provide something with acceptable usability that encourages exploration and playing around.

I’d also appreciate it if you could respond to my other email with your thoughts:
https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20171127/041868.html

-Chris

···

On Dec 1, 2017, at 10:39 AM, Douglas Gregor <dgregor@apple.com> wrote:

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.

I may have spoken incorrectly but I do still believe it is contrary to what I (and apparently many others) feel is the spirit of Swift.

As I understand it, AnyObject lookup was a necessity during the historical development of Swift. The attempts to narrow its scope have merit regardless of how far they can go. They indicate that perhaps it is a feature we would remove if that were possible. I would love to see that happen someday (although I know it is unlikely), or at least see it be made explicit at the call site. I suspect if this feature did not exist and it was proposed today it would be met with at least as much resistance as your proposals have.

This isn’t the way I see it. You’re right that we want to reduce AnyObject dispatch wherever possible: types are good after all. Swift on Linux doesn’t even have AnyObject dispatch, so you can tell that it is a means to an end, not a feature that we think is great for Swift on its own merits.

It’s a means to an end and the need for that end is growing smaller over time.

What is that end? It is interoperability with truly dynamic values, which do occur in Objective-C, where pervasive casting would harm code clarity and usability. The desire to fix AnyObject you are seeing are related to two different issues: 1) The design of AnyObject dispatch itself is problematic in some ways, and 2) many values in ObjC APIs were typed as id simply because there was no way to describe a more specific type given Objective-C’s original type system (which has been improved). However, given the presence of actually polymorphic values being used by some APIs, I don’t imagine it going away entirely.

Which is a reason to consider making this mechanism more visible in the syntax.

In the case of other dynamic languages, the situation is even more severe. Some dynamic languages have progressive type systems available, but not all do. Those which do have to deal with the fact that the base languages were not designed for typing systems, which is far more severe a problem than Objective-C’s situation.

If you haven’t already, I encourage you to read the mypy docs (http://mypy.readthedocs.io/en/stable/index.html\) to understand how the type system they’re trying to build on top of Python works. They have to contend with things like:

- the single array subscript operator taking both scalars and ranges, and returning different type results depending on their input (Python has no overloading)
- pervasive use of duck typing, e.g. int is duck type compatible with float and complex (duck typing is for many non-primitive types as well)
- pervasive use of variance
- APIs creep to “just work” when passed all sorts of weird values, meaning that their API contract is extremely loose.
- Python supports *dynamically computed base classes* and lots of other crazy things.
- and more..

Mypy also only supports a subset of Python - it doesn’t support properties with setters as one example, and its generic and class system is only partially implemented. The only way to use it in practice is to use their ability to silence warnings, it is not an acceptable basis for a sound type system that we could depend on in Swift.

For all those reasons, we really do need something like AnyObject dispatch if we care about working with dynamically typed languages. The design I’m suggesting carefully cordons this off into its own struct type, so it doesn’t infect the rest of the type system, and is non-invasive in the compiler.

I am quite familiar with dynamic languages and agree that this is necessary if we are going to fully open up access to these languages from 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.

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.

The primary problem I have is that misuse is too easy and too subtle. It is at least as easy to misuse as other features in Swift which have been carefully designed to be obvious at usage sites.

I was happy to see that you are willing to consider Xiaodi’s suggestion to require conformance to be stated as part of the original type declaration. That would be a significant improvement to the proposal IMO.

I’ve revised the proposal to require this.

Thank you!

I strongly urge you to reconsider the decision of that dynamic members must be made available with no indication at usage sites. An indication of dynamic lookup at usage sites aligns very well (IMO) with the rest of Swift (AnyObject lookup aside) by calling attention to code that requires extra care to get right.

I don’t understand this. The proposal is fully type safe, and this approach is completely precedented by AnyObject. Swift’s type system supports many ways to express fallibility, and keeping those decisions orthogonal to this proposal is the right thing to do, because it allows the author of the type to decide what model makes sense for them.

Allowing the author of the type to choose whether the mechanism is hidden or visible is exactly what I don’t want to allow. I think you have the right design regarding types and semantics - the author chooses. But I don’t want these calls to look like ordinary member lookup when I’m reading code.

They inherently have a much greater chance of failure than ordinary member lookup. Further, authors are likely to choose immediate traps or nil IUO as failure modes as forcing users to deal with Optional on every call is likely to be untenable. I believe this behavior should be represented by some kind of syntax at the usage site. I don’t believe it is an undue burden. It would make the dynamic lookup semantic clear to all readers and would help to discourage abuse.

In any case, I hear loud and clear that there is concern about possible abuse of this proposal, so I added a section in the alternatives section to discuss further ways to reduce possibility for abuse:
https://gist.github.com/lattner/b016e1cf86c43732c8d82f90e5ae5438#reducing-potential-abuse

More suggestions are welcome.

Thank you for adding this section. It strengthens the proposal to have this perspective represented in some fashion.

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.

I’ll mostly let Doug continue the discussion of the approach he is suggesting. It looks to me like that is an approach that might help in this area. For example, if a symbol is removed it may be possible to produce a compiler error in at least some cases. I don’t think Doug’s suggestion precludes full dynamism but would offer a much better experience in at least some cases. Perhaps it would even be enough to call into question whether full dynamism is really necessary or not (as Doug seems to think). We won’t know for sure without exploring this further.

I understand that you believe the direction he has advocated is not realistic. Perhaps, but I do appreciate that the conversation is happening before a final decision is made. I support further exploration of this at least as long as Doug believes it is a good idea to do so.

Yes, I agree, it is important to get aligned on this.

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.

One of the stated aims of this proposal is to bring many new programmers into the Swift community. That is a wonderful goal!

Of course that may include programmers with a different set of technical values than are currently predominant in the Swift community. If we make it really easy to use Swift as a highly dynamic language we may soon see a large sub-community form that uses Swift in this way. Regardless of how you would feel about this I think it’s an important possible outcome to consider.

You mentioned that many of them may prefer proper Swift APIs for the libraries they use and are hoping that the issues with dynamism will incentive them to create these in time. Perhaps requiring some kind of call-site annotation would increase this incentive without being significant enough to disincentive using these libraries in Swift.

I think you over-estimate how awesome Python (and other language APIs) are going to be, even with these two proposals. Keep in mind that Python has the GIL for example, the naming conventions and design patterns are completely different, etc. Even with both of these proposals, there will be high incentive for building pure Swift APIs to replace things over time. The situation is similar to C interop with Swift: it is incredibly important that it exist, but people aren’t exactly aiming to use UnsafePointer for everything :-). It is possible someone would do so, but people do all sorts of bad things locally in their codebase. We cannot prevent that.

In fact, I would argue that making Python interoperability *too good* is the bigger risk we have. If there is no reason for people to move off of using those APIs, they will continue to do so forever. That isn’t a transition path, that’s a merger of communities.

I’m not sure why you think I’m over-estimating this. The suggestion above is in fact partly that requiring usage site annotation would help ensure that Python interoperability is too good. This will be a subtle but regular reminder that it might be good to at least wrap up usage in an overlay to isolate and reduce the prevalence of dynamic lookup.

I’m also suggesting that it’s possible that some of the programmers interested in using Swift in this way would also appreciate making this clear at the usage site. I think it’s at least worth finding out.

In particular, if you continue to insist on standard dot syntax for member lookup I would appreciate hearing more about why that is a sticking point for you, especially in light of your recent comment about people looking to move away from Python.

Thanks, I addressed this in the alternative section.

I appreciate this, thank you.

Swift's type system already includes features (optionals, IUOs, runtime failure) for handling failability. Keeping that orthogonal to this proposal is good because that allows API authors to make the right decision for expressing the needs of their use-case.

As noted above, I think the semantics of the lookup and result of invoking the member itself are orthogonal to the visibility of the dynamic lookup mechanism at the usage site. I’m not asking you to impose any semantics on library authors, only to make the mechanism of lookup visible.

Swift already has a dynamic member lookup feature, "AnyObject dispatch" which does not use additional punctuation, so this would break precedent.

I would prefer if dynamic lookup were visible with AnyObject as well. For that reason I don’t believe it makes a good precedent to follow. In fact, I would prefer to see us go the other direction and perhaps even consider revising dynamic lookup syntax for AnyObject in the future.

The point of this proposal is to make use of dynamic language APIs more elegant than what is already possible: making use of them ugly (this punctuation character would be pervasive through use of the APIs and just add visual noise, not clarity) undermines the entire purpose of this proposal.

It will make them significantly more elegant than what is already possible with or without one additional character. I don’t think it undermines the proposal at all. I think it strikes the best balance between dynamic lookup semantics and clarity at the usage site. This is a semantic that I as a reader really want to see clearly when it is used. Aesthetics are important until they interfere with clarity.

This can be a difficult balance to strike and I am arguing for a different balance than you seem to prefer. If this proposal is to be accepted the community and the core team will have to decide what balance to strike. I think it’s a good idea to consider it carefully.

···

On Dec 2, 2017, at 2:44 PM, Chris Lattner <clattner@nondot.org> wrote:
On Dec 1, 2017, at 5:50 PM, Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> wrote:

-Chris

This is not a “footgun” in the traditional sense, because it does not affect someone who does not actively use it. It is not like UB in C.

The strongest your argument can be is “someone could use dynamic member lookup in their API to produce an API with a footgun that hurts their users”. I submit for your consideration that there are lots and lots of ways that people can create poor APIs that hurt users. If someone uses this feature inappropriately, then their API is crappy and you shouldn’t use it, just like any other misuse of languages features.

I also haven’t seen demonstration of an example where someone would non-maliciously [1] use it in an API, where it would cause harm, and what kind of harm that would be. You are creating a boogieman without taking any effort to explain the problem, so there is no way to do a cost benefit tradeoff analysis of whether the “benefit” of this feature is worth the “cost” that you claim exists.

I have said everything I intend to say about this topic.

-Chris

[1] Obviously if the API you are using was maliciously crafted, then you have tons of other problems.

···

On Dec 4, 2017, at 1:42 AM, Vincent Esche via swift-evolution <swift-evolution@swift.org> wrote:

I think the argument basically is "let's not add another footgun" (because the design of Swift , for example regarding null handling, is to have less footguns than other languages). The fact that there are footguns in swift isn't an argument for adding new ones.

Couldn’t have said it better. This is what it all boils down to.

May the question is a little silly. But can this proposal be made on top of
property behaviors? ( or the inverse).

We already talk about *new kinds* of getters and setters, possibly new
sintaxes to call then... I don’t now, maybe I’m overlooking the problem,
but feels that , wherever we choose here should be userfull for other
things in the language.

···

Em seg, 4 de dez de 2017 às 06:42, Vincent Esche via swift-evolution < swift-evolution@swift.org> escreveu:

I think the argument basically is "let's not add another footgun" (because

the design of Swift , for example regarding null handling, is to have less
footguns than other languages). The fact that there are footguns in swift
isn't an argument for adding new ones.

Couldn’t have said it better. This is what it all boils down to.

On Mon, Dec 4, 2017 at 12:16 AM, Benjamin G via swift-evolution < > swift-evolution@swift.org> wrote:

On Sun, Dec 3, 2017 at 8:26 PM, Chris Lattner via swift-evolution < >> swift-evolution@swift.org> wrote:

On Dec 3, 2017, at 11:03 AM, Magnus Ahltorp <map@kth.se> wrote:
>
>> 4 Dec. 2017 02:40 Chris Lattner via swift-evolution <
swift-evolution@swift.org> wrote:
>>
>> 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.
>
> This example shows what many on this list don't believe: that any
Swift method or member access can fail. If the return value of this "get"
method is an IUO, or not an Optional at all, and doesn't throw, then the
expression would have to fail hard if "foo" didn't resolve to something
meaningful.
>
> The most common argument against this proposal is that someone could
make an API using Dynamic Member Lookup that could fail even though it is
not apparent to the caller. But, as we see in the example, this is just as
possible today.

Correct. The argument also fails to recognize that (when bridging to a
dynamic language):

        x+y

Is a completely dynamic method call which can fail (or return IUO), as
is:

        x[i]

And that this is true with no changes to Swift. The claim that such a
thing is counter to the design of Swift is completely perplexing to me.

I think the argument basically is "let's not add another footgun"
(because the design of Swift , for example regarding null handling, is to
have less footguns than other languages). The fact that there are footguns
in swift isn't an argument for adding new ones.

-Chris

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

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

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

I think the argument basically is "let's not add another footgun" (because the design of Swift , for example regarding null handling, is to have less footguns than other languages). The fact that there are footguns in swift isn't an argument for adding new ones.

Couldn’t have said it better. This is what it all boils down to.

I would definitely not go as far as calling this proposal a language footgun.

···

On 4 Dec 2017, at 10:42, Vincent Esche via swift-evolution <swift-evolution@swift.org> wrote:

On Mon, Dec 4, 2017 at 12:16 AM, Benjamin G via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Sun, Dec 3, 2017 at 8:26 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
On Dec 3, 2017, at 11:03 AM, Magnus Ahltorp <map@kth.se <mailto:map@kth.se>> wrote:
>
>> 4 Dec. 2017 02:40 Chris Lattner via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
>>
>> 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.
>
> This example shows what many on this list don't believe: that any Swift method or member access can fail. If the return value of this "get" method is an IUO, or not an Optional at all, and doesn't throw, then the expression would have to fail hard if "foo" didn't resolve to something meaningful.
>
> The most common argument against this proposal is that someone could make an API using Dynamic Member Lookup that could fail even though it is not apparent to the caller. But, as we see in the example, this is just as possible today.

Correct. The argument also fails to recognize that (when bridging to a dynamic language):

        x+y

Is a completely dynamic method call which can fail (or return IUO), as is:

        x[i]

And that this is true with no changes to Swift. The claim that such a thing is counter to the design of Swift is completely perplexing to me.

I think the argument basically is "let's not add another footgun" (because the design of Swift , for example regarding null handling, is to have less footguns than other languages). The fact that there are footguns in swift isn't an argument for adding new ones.

-Chris

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

The strongest your argument can be is “someone could use dynamic member lookup in their API to produce an API with a footgun that hurts their users”. I submit for your consideration that there are lots and lots of ways that people can create poor APIs that hurt users. If someone uses this feature inappropriately, then their API is crappy and you shouldn’t use it, just like any other misuse of languages features.

I wish that attitude had more weight in the past, when things like forbidding inheritance or adding warnings for unused results were discussed…
You can write stupid code in every real programming language, no matter how safe it claims to be, so you can’t argue against a feature just by pointing out that it can be misused.

But I guess many people got the impression that Swift tries very hard to protect us:
Things like let and struct help ensuring that things aren’t changed unexpectedly (and sometimes, you are forced to acknowledge that a parameter can be changed with „&“), Optionals help to eliminate nil-value problems, and a strong type system protects us from trying to call methods that aren’t there.
Now we are talking about something that allows us to call methods that "aren’t there“, and although I wouldn’t call it a footgun, it’s definitely a huge change for the language. Even if it is no contradiction to the spirit of Swift (who could decide that if not you?), big changes tend to make people afraid — especially under the prospect that they might come before next year, and will stay forever.
Yes, dynamic dispatch isn’t completely new to Swift, but you barely notice that Cocoa-types have special rules (unless you have to keep it in mind because you implement those rules in the compiler).

The two proposals have benefits, but they have risks as well — and imho nobody can foresee their full impact now.
Therefor, I’m convinced they should not be accepted under the normal terms:
Look back at what renaming private to fileprivate did to Swift, and all the churn that could have been avoided with a trial phase.

static type checking is no guarantee of program correctness.

you can take a correct, functioning program in Java, for example, strip out all the type declarations and change the syntax to Groovy, and still have a correct, functioning program.

(if you avoid methods overloaded on parameter type)

with polymorphism via protocols, classes, etc., we don't have guarantees that the method invoked at runtime is actually going to behave correctly because behavior != type.

polymorphism gives us advantages that make up for the lack of determinism.

the *code* calling a method should not have to care whether the object is using static dispatch, dynamic dispatch, or even remote-procedure calls. I kinda miss Objective-C's "Dynamic Objects" proxy objects which could forward method calls across process boundaries and over networks.

obviously the *programmer* cares about additional failure cases involved for remote proxies, but there's no reason for different *syntax*.

C. Keith Ray
What Every Programmer Needs To… by C. Keith Ray [PDF/iPad/Kindle] <- buy my book?
http://agilesolutionspace.blogspot.com/
twitter: @ckeithray
http://www.thirdfoundationsw.com/keith_ray_resume_2014_long.pdf

···

On Dec 5, 2017, at 11:13 AM, Thorsten Seitz via swift-evolution <swift-evolution@swift.org> wrote:

Am 03.12.2017 um 20:04 schrieb Magnus Ahltorp via swift-evolution <swift-evolution@swift.org>:

4 Dec. 2017 02:40 Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:

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.

This example shows what many on this list don't believe: that any Swift method or member access can fail. If the return value of this "get" method is an IUO, or not an Optional at all, and doesn't throw, then the expression would have to fail hard if "foo" didn't resolve to something meaningful.

The most common argument against this proposal is that someone could make an API using Dynamic Member Lookup that could fail even though it is not apparent to the caller. But, as we see in the example, this is just as possible today.

I just wanted to add that the single purpose of a static type system is to ensure that the methods being called on a receiver are present at runtime and take arguments of the types known at compile time. Of course the type system does not guarantee that those calls to not fail. Even in Haskell there is no guarantee that a function call does not fail as it allows partial patterns (therefore `head ` crashes). Or a function call might enter an infinite loop. But, to repeat, the type system guarantees that the method or member itself will be present.

Therefore I cannot follow the argument that it follows from the possible failure of a Swift method or member access that it is ok or even a comparable type of failure if the method or member itself is not present. This would imply that Swift’s static type system is unnecessary. Why check for the presence of a method or member if calling it or accessing it might fail anyway? No, I do not buy that argument. And I do not think that I have to provide examples where a static type system is of help.

The remaining question therefore is whether it is ok to remove the static type system for certain isolated use cases and that might be the case. But I like others would prefer if those use cases would be isolated somehow visually. Someone proposed to require `dynamic` before expressions containing dynamic member lookups similar to `try`, with the additional option to enclose a whole block into `dynamic { … }` to express the same as prefixing each expression with `dynamic`. I still think this has merit:

let result = dynamic x.foo.bar  // will crash if foo or bar are not present

let result = dynamic? x.foo.bar // will return nil if foo or bar are not present

// will crash if foo or bar are not present
let result = dynamic {
	let y = x.foo.bar
	let z = y.baz(42)
	return z.zork()
}

// will return nil if foo or bar are not present
let result = dynamic? {
	let y = x.foo.bar
	let z = y.baz(42)
	return z.zork()
} 

It allows to clearly demarcate the border between regions which are fully statically type checked and those where the static type system be only partially available (depending on the types of the respective receivers) while not being very intrusive due to the block notation.
Of course I can still have a mixture of static and dynamic member accesses _within_ a dynamic block but at least I know that when I am outside of such a block there won’t be any dynamic member accesses. None. Zero.

-Thorsten

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

Under the proposal given here, the compiler doesn’t know what will happen when “foo” or “bar” are not present. This proposal does not even require the implementation to report if a member is present or not, and there is certainly no guarantee of a crash in such a case. The only thing the compiler knows is that subscript(dynamicMember:) will be called.

Consider the following implementation:

struct PyVal: DynamicMemberLookupProtocol {
    
    subscript(dynamicMember: String) -> PyVal? {
        if let pythonMember = python_c_api.get(dynamicMember) {
            return PyVal(pythonMember)
        }
        else {
            return nil
        }
    }
}

let result = x.foo?.bar

There is no crashing here; result will be an optional type, and will be nil if the requested property does not exist. Some other use of DynamicMemberLookupProtocol might accept any value, and just log it to a file! In that case, neither crashing nor returning an optional is required.

-BJ

···

On Dec 5, 2017, at 12:13 PM, Thorsten Seitz via swift-evolution <swift-evolution@swift.org> wrote:

let result = dynamic x.foo.bar // will crash if foo or bar are not present

let result = dynamic? x.foo.bar // will return nil if foo or bar are not present