On 30.11.2017 17:15, Matthew Johnson via swift-evolution wrote:
Sent from my iPad
On Nov 30, 2017, at 2:24 AM, Douglas Gregor via swift-evolution <swift-evolution@swift.org
<mailto:swift-evolution@swift.org>> wrote:
On Nov 26, 2017, at 10:04 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org
<mailto:swift-evolution@swift.org>> wrote:
I’d like to formally propose the inclusion of user-defined dynamic member
lookup types.
Here is my latest draft of the proposal:
https://gist.github.com/lattner/b016e1cf86c43732c8d82f90e5ae5438
https://github.com/apple/swift-evolution/pull/768
An implementation of this design is available here:
https://github.com/apple/swift/pull/13076
The implementation is straight-forward and (IMO) non-invasive in the compiler.
I think better interoperability with Python (and other OO languages in widespread use) is a good goal, and I agree
that the implementation of the feature described is straight-forward and not terribly invasive in the compiler.
However, I do not think this proposal is going in the right direction for Swift. I have objections on several
different grounds.
+1 to everything Doug says here. He articulates the concerns I tried to voice in an earlier thread more clearly and
more thoroughly.
This design introduces a significant hole in the type system which is contrary to the spirit of Swift. It doesn’t just
make dynamic language interop easy, it also changes the semantics of any type which conforms to
DynamicMemberLookupProtocol. That is not something that is locally visible when looking at a piece of code. Worse, it
does so in a way that trivial mistakes such as a typo can turn into runtime errors. Worst of all, as Doug points out it
is possible to use retroactive conformance to change the semantics of vast quantities of widely used types in one fell
swoop.
One additional concern that I don’t believe has been mentioned is that of evolution. When a library is imported from a
dynamic language and the library makes breaking changes Swift code written using this feature will break without notice
until a runtime error occurs. Is that acceptable? That may be what users of dynamic languages are accustomed to but
can we do better in Swift? If we can make the process of upgrading dependencies less a less harrowing experience
perhaps that could be a major selling point in moving them to Swift. But we won’t accomplish that with this design.
Interop with dynamic languages is a worthy goal, but we should do it in a way that has no potential impact on code that
does not need the feature. A semantic variant of “don’t pay for what you don’t use” should apply here. If we add this
feature everyone will pay for it every time they read unfamiliar Swift code. They will have to contend with the
potential for conformances to this protocol, even on standard library types. That feels like a pretty high cost to me,
especially for programmers who aren’t using the dynamic language interop features themselves.
Further, a consistent theme has been the desire to avoid things like compiler flags that could fragment the community.
I fear this feature has the potential to do that, especially if it is really successful at attracting people from the
dynamic language community. I think we should consider carefully the following questions:
* Do we want to encourage these programmers to continue writing code in an extremely dynamic style when they come to
Swift or do we want to encourage them to embrace the advantages of relying on the type system?
* If we want them to embrace the type system but that would keep them away from
Swift how do we feel about that?
FWIW +1 for Doug, Matthew and others said.
I believe it is not possible to express each variation of other language's syntax/behavior in Swift - in any case
languages will differ in some details, function/properties calling/assignment rules etc. Also, we need to remember Swift
exceptions, proposed 'async' feature, KeyPaths etc and map these to features of selected dynamic language. In result
we'll have not-Swift-not-Python/whatever frankenstein code :-)
Probably it is better just to allow Python/other code inside swift code, be able to pass Swift instances and return some
"result" from that code to Swift and be able to convert it then to standard Swift types.
// special syntax for other language's code
// raw dynamic language's code after "in" and before last bracket
// can have IDE/editor support for that language with all features standard for
that language, like autocomplete
let code = {(name: String)->[Int] throws in @language(Python)
// some raw Python code, indent is required
}
let result = try? code(name: "John")
or at least
// special syntax for other language's code
// raw dynamic language's code after "\\**PYTHON" line and before "**//"
// can have IDE/editor support for that language with all features standard for
that language
// "\\**" and "**//" are fictional syntax for "raw" strings(without escaping,
no interpolation)
let code = \\**PYTHON
// some raw Python code
**//
// type(of: code) == String, 'PYTHON' is just allowed comment which can be used
by IDE/editor
let result = try? PythonEngine.execute(code, externalParams: ["name": "John"])
as [Int]
(the syntax was not carefully thought out.)
IMO if we actually need such kind of interop with dynamic languages, it should be in very explicit form, with special
syntax, saying "this 'thing' will be resolved dynamically, at run time, nothing is guaranteed in compile time, be aware".
Just my 2 cents.
Vladimir.
Matthew
*Philosophy*
Swift is, unabashedly, a strong statically-typed language. We don’t allow implicit down casting, we require “as?” so
you have to cope with the possibility of failure (or use “as!” and think hard about the “!”). Even the gaping hole
that is AnyObject dispatch still requires the existence of an @objc declaration and produces an optional lookup
result, so the user must contend with the potential for dynamic failure. Whenever we discuss adding more dynamic
features to Swift, there’s a strong focus on maintaining that strong static type system.
IMO, this proposal is a significant departure from the fundamental character of Swift, because it allows access to
possibly-nonexistent members (as well as calls with incorrect arguments, in the related proposal) without any
indication that the operation might fail. It’s easy to fall through these cracks for any type that supports
DynamicMemberLookupProtocol—a single-character typo when using a DynamicMemberLookupProtocol-capable type means you’ve
fallen out of the safety that Swift provides. I think that’s a poor experience for the Python interoperability case,
but more on that in the Tooling section below.
While we shouldn’t necessarily avoid a feature simply because it can be used
distastefully, consider something like this:
public extension NSObject : DynamicMemberLookupProtocol,
DynamicCallableProtocol { … }
that goes directly to the Objective-C runtime to resolve member lookups and calls—avoiding @objc, bridging headers,
and so on. It’s almost frighteningly convenient, and one could imagine some mixed Objective-C/Swift code bases where
this would save a lot of typing (of code)… at the cost of losing static typing in the language. The presence of that
one extension means I can no longer rely on the safety guarantees Swift normally provides, for any project that
imports that extension and uses a subclass of NSObject. At best, we as a community decide “don’t do that”; at worse,
some nontrivial fraction of the community decides that the benefits outweigh the costs (for this type or some other),
and we can no longer say that Swift is a strong statically-typed language without adding “unless you’re using
something that adopts DynamicMemberLookupProtocol”.
*Tooling*
The Python interoperability enabled by this proposal *does* look fairly nice when you look at a small,
correctly-written example. However, absolutely none of the tooling assistance we rely on when writing such code will
work for Python interoperability. Examples:
* As noted earlier, if you typo’d a name of a Python entity or passed the wrong number of arguments to it, the
compiler will not tell you: it’ll be a runtime failure in the Python interpreter. I guess that’s what you’d get if you
were writing the code in Python, but Swift is supposed to be *better* than Python if we’re to convince a community to
use Swift instead.
* Code completion won’t work, because Swift has no visibility into declarations
written in Python
* Indexing/jump-to-definition/lookup documentation/generated interface won’t ever work. None of the IDE features
supported by SourceKit will work, which will be a significant regression for users coming from a Python-capable IDE.
Statically-typed languages should be a boon for tooling, but if a user coming from Python to Swift *because* it’s
supposed to be a better development experience actually sees a significantly worse development experience, we’re not
going to win them over. It’ll just feel inconsistent.
*Dynamic Typing Features*
It’s possible that the right evolutionary path for Swift involves some notion of dynamic typing, which would have a
lot of the properties sought by this proposal (and the DynamicCallableProtocol one). If that is true—and I’m not at
all convinced that it is—we shouldn’t accidentally fall into a suboptimal design by taking small, easy, steps. If
we’re to include dynamic-typing facilities, we should look at more existing practice—C# ‘dynamic' is one such
approach, but more promising would be some form of gradual typing a la TypeScript that let’s one more smoothly (and
probably explicitly) shift between strong and weak typing.
*How Should Python Interoperability Work?*
Going back to the central motivator for this proposal, I think that providing something akin to the Clang Importer
provides the best interoperability experience: it would turn Python declarations into *real* Swift declarations, so
that we get the various tooling benefits of having a strong statically-typed language. Sure, the argument types will
all by PyObject or PyVal, but the names are there for code completion (and indexing, etc.) to work, and one could
certainly imagine growing the importer to support Python’s typing annotations
<https://docs.python.org/3/library/typing.html>. But the important part here is that it doesn’t change the language
model at all—it’s a compiler feature, separate from the language. Yes, the Clang importer is a big gnarly beast—but if
the goal is to support N such importers, we can refactor and share common infrastructure to make them similar, perhaps
introducing some kind of type provider infrastructure to allow one to write new importers as Swift modules.
In truth, you don’t even need the compiler to be involved. The dynamic “subscript” operation could be implemented in a
Swift library, and one could write a Python program to process a Python module and emit Swift wrappers that call into
that subscript operation. You’ll get all of the tooling benefits with no compiler changes, and can tweak the wrapper
generation however much you want, using typing annotations or other Python-specific information to create better
wrappers over time.
- Doug
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution