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

Michel Fortin michel.fortin at michelf.ca
Sun Dec 3 10:33:26 CST 2017


> Le 2 déc. 2017 à 12:20, Chris Lattner via swift-evolution <swift-evolution at swift.org> a écrit :
> 
>> *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.

I'm not sure if that's what Doug was aiming at, but I read it to mean we could add a new import syntax like this one:

	import py2swift("DogModule")

Instead of looking for a module in the _module search path_, the compiler would run a command-line tool called `py2swift` found in an _import tool search path_. The tool generates the module on the fly before returning the module path to the compiler, who can then continue with compiling.

In the case above, the `py2swift` script would receive "DogModule" as its argument, introspect the Python module, generate bindings, and output the path to the generated binding module for the Swift compiler to use. The generated code for the binding module could look like this:

	import PyRuntimeGlue

	final class Dog {
		private let pyVal: PyVal
		init(_ pyVal: PyVal) { self.pyVal = pyVal }
		init(_ name: String) { pyVal = pyRuntimeNew(class: "Dog", name) }
		func add_trick(_ trick: PyVal) { pyRuntimeCall(pyVal, "add_trick", trick) }
	}

and below the import line the usage would be the same as in your proposal:

	import py2swift("DogModule") // auto-generate bindings module

	let dog = Dog("Brianna")
	dog.add_trick("Roll over")
	let dog2 = Dog(Kaylee").add_trick("snore")

Except now `Dog` is a real type instead of a variable masquerading as a type, auto-completion will work the way you'd expect, and you'll get a compile-time error if there is no Python method with that name on that type.

There's still one problem though: Python functions returning a Dog will actually return you something of type PyVal. That'll force you to rewrap the value into the right type everywhere. Example:

	let dog = Dog(owner.dog)
	dog.add_trick("snore") // Dog.add_trick gets called

The binding generator could take the AnyObject approach and have the binding module insert extensions for all methods in addition to the type:

	import PyRuntimeGlue

	final class Dog {
		private let pyVal: PyVal
		init(_ pyVal: PyVal) { self.pyVal = pyVal }
		init(_ name: String) { pyVal = pyRuntimeNew(class: "Dog", name) }
		func add_trick(_ trick: PyVal) { pyRuntimeCall(pyVal, "add_trick", trick) }
	}
	extension PyVal {
		func add_trick(_ trick: PyVal) { pyRuntimeCall(pyVal, "add_trick", trick) }
	}

and now you can do:
	
	let dog = owner.dog
	dog.add_trick("snore") // PyVal.add_trick gets called

Does that makes sense?

-- 
Michel Fortin
https://michelf.ca



More information about the swift-evolution mailing list