[swift-evolution] Proposal: Introduce User-defined "Dynamic Member Lookup" Types

Karl Wagner razielim at gmail.com
Sun Dec 3 14:20:23 CST 2017

> I believe that adding explicit syntax would be counterproductive to your goals, and would not make dynamic lookup syntax more clear.  I assume that you would also want the same thing for DynamicCallable too, and operator overloads, subscripts, and every other operation you perform on these values, since they all have the exact same behavior.
> If we required some syntax even as minimal as “foo.^bar” and "baz^(42)”, that change would turn this (which uses runtime failing or IUO return values like AnyObject):
> 	let np = Python.import("numpy")
> 	let x = np.array([6, 7, 8])
> 	let y =  np.arange(24).reshape(2, 3, 4)
> 	let a = np.ones(3, dtype: np.int32)
> 	let b = np.linspace(0, pi, 3)
> 	let c = a+b
> 	let d = np.exp(c)
> 	print(d)
> into:
> 	let np = Python.import("numpy")
> 	let b = np^.array^([6, 7, 8])
> 	let y =  np^.arange^(24)^.reshape^(2, 3, 4)
> 	let a = np^.ones^(3, dtype: np^.int32)
> 	let b = np^.linspace^(0, pi, 3)
> 	let c = a+^b
> 	let d = np^.exp^(c)
> This does not improve clarity of code, it merely serves to obfuscate logic.  It is immediately apparent from the APIs being used, the API style, and the static types (in Xcode or through static declarations) that this is all Python stuff.  When you start mixing in use of native Swift types like dictionaries (something we want to encourage because they are typed!) you end up with an inconsistent mismash where people would just try adding syntax or applying fixits continuously until the code builds.

That’s not Swift. You just wrote a bunch of Python. For example, Swift has a native Int32.+ operator which fails on overflow - does your example also do that? Anybody’s guess! Does your numpy array conform to Collection? I guess not, because it’s an opaque Python value.

That’s exactly the kind of stuff I, as a user of the language, really don't want to see mixed together with real Swift. I appreciate the need to use functionality from libraries written in Python, but I don’t appreciate it being so invisible and pervasive throughout the language. If you have a bunch of Python logic, I’d prefer you wrote as much of it as possible in Python, with as few bridging points to Swift as you can get away with. I remain convinced that this design encourages the opposite - because, as you said earlier, it’s “too good”.

As for the point about Swift already including non-marked, potentially-crashing operations (like the + operator, or Array subscripting): nobody likes that behaviour! Whenever I come to a new Swift codebase, I almost universally find that people have written their own “safe” Array accessor which integrates bounds-checking and returns an Optional. The issue has come up here many, many times for inclusion in the standard library. I certainly would not use it as justification for adding more of those kinds of unmarked, potentially-unsafe operations. Also, enough Swift developers know about the Array subscript behaviour that the square brackets almost become a marker, like “!”, of a potentially-failing operation. The same is not true of the dot operator, in general.

I also don’t agree with the comparisons to Objective-C/AnyObject dispatch. It’s true that it’s unsafe to an extent, but it’s also orders of magnitude safer than this sort of dispatch. Clang is integrated in to the compiler, and can at least perform some rudimentary checking of method signatures/selectors. This sort of dispatch provides absolutely no protections whatsoever — is “arange” really a function? Is it not really a typo for “arrange”? That’s something I need to Google. With regular Swift I can assume that if the compiler allows it, there is a function called “arange” somewhere, and all I need to worry about is whether the erased AnyObject is of the correct type to respond to that message. And as I said earlier, AnyObject is incredibly rare in practice anyway. So no, I don’t agree that we should just keep lowering the safeguards; it’s like demolishing your house because of one draughty door.

What I could support, would be some kind of optional syntax, possibly with some kind of associated scope which allows you omit it. Something like:

// Normally, optionals are required.
let result: PythonObject? = pythonObj.someProperty?.someFunction(1, 2, 3)

// Within a special scope, you can omit them. The scope will bail at the first lookup failure and return nil.
let result: PythonObject? = Python {
    return pythonObj.someProperty.someFunction(1, 2, 3)

Perhaps the “Python” object could conform to a protocol with an associated type for the objects it can implicitly unwrap. There would be some additional compiler work, for sure, but that’s secondary to a good language model IMO (easy for me to say, I know).

- Karl
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20171203/18a112f0/attachment.html>

More information about the swift-evolution mailing list