> On Nov 26, 2017, at 10:04 PM, Chris Lattner via swift-evolution 
> <swift-evolution@swift.org> wrote:
> I’d like to formally propose the inclusion of user-defined dynamic member 
> lookup types.
> Here is my latest draft of the proposal:
> https://gist.github.com/lattner/b016e1cf86c43732c8d82f90e5ae5438
> 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.

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

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

Reply via email to