> On Oct 31, 2017, at 12:43 AM, Chris Lattner <clatt...@nondot.org> wrote:
> 
> JohnMC: question for you below.
> 
> On Oct 30, 2017, at 1:25 PM, Douglas Gregor <dgre...@apple.com 
> <mailto:dgre...@apple.com>> wrote:
>>> 
>>> Thinking about the Perl case makes it clear to me that this should not be 
>>> built into the compiler as a monolithic thing.  Perl supports several 
>>> different types (SV/AV/HV) which represent different concepts (scalars, 
>>> arrays, hashes) so baking it all together into one thing would be the wrong 
>>> way to map it.  In fact, the magic we need is pretty small, and seems 
>>> generally useful for other things. Consider a design like this:
>>> 
>>> 
>>> // not magic, things like Int, String and many other conform to this. 
>>> protocol Pythonable {
>>>  init?(_ : PythonObject)
>>>  func toPython() -> PythonObject
>>> }
>> 
>> It’s not magic unless you expect the compiler or runtime to help with 
>> conversion between Int/String/etc. and PythonObject, as with 
>> _ObjectiveCBridgeable.
> 
> Right, as I said above “not magic”.  The conformances would be manually 
> implemented in the Python overlay.  This provides a free implicit conversion 
> from "T -> Pythonable” for the T’s we care about, and a failable init from 
> Python back to Swift types.
> 
>>> // Not magic.
>>> struct PythonObject : /*protocols below*/ {
>>>   var state : UnsafePointer<PyObject>
>>> 
>>>   subscript(_ : Pythonable…) -> PythonObject {
>>>     ...
>>>   }
>>> }
>>> 
>>> // Magic, must be on the struct definition.  
>>> // Could alternatively allow custom copy/move/… ctors like C++.
>>> protocol CustomValueWitnessTable {
>>>  static func init(..)
>>>  static func copy(..)
>>>  static func move(..)
>>>  static func destroy(..)
>>> }
>> 
>> Swift’s implementation model supports this. As a surface-level construct 
>> it’s going to be mired in UnsafeMutablePointers, and it’s not at all clear 
>> to me that we want this level of control there. 
> 
> There are two ways to implement it:
> 1) static func’s like the above, which are implemented as UnsafePointer's
> 2) Proper language syntax akin to the C++ “rule of 5”.
> 
> The pro’s and con’s of the first approach:
> 
> pro) full explicit control over what happens
> pro) no other new language features necessary to implement this.  The second 
> approach would need something like ownership to be in place.
> con) injects another avenue of unsafety (though it would be explicit, so it 
> isn’t that bad).  It isn’t obvious to me that approach #2 can be safe, but I 
> haven’t thought about it enough.
> ???) discourages people from using this protocol because of its explicit 
> unsafety.
> 
> I can think of two things that could tip the scale of the discussion:
> 
> a) The big question is whether we *want* the ability to write custom 
> rule-of-5 style behavior for structs, or if we want it to only be used in 
> extreme cases (like bridging interop in this proposal).  If we *want* to 
> support it someday, then adding proper “safe” support is best (if possible).  
> If we don’t *want* people to use it, then making it Unsafe and ugly is a 
> reasonable way to go.
> 
> b) The ownership proposal is likely to add deinit's to structs.  If it also 
> adds explicit move initializers, then it is probably the right thing to add 
> copy initializers also (and thus go with approach #2).  That said,  I’m not 
> sure how the move initializers will be spelled or if that is the likely 
> direction.  If it won’t add these, then it is probably better to go with 
> approach #1.  John, what do you think?

I was hoping not to have to add explicit move/copy initializers, perhaps ever.  
I would suggest one of two things:
  - We could add a Builtin type for these types in Swift.  Because of our 
architecture, this is mostly an IRGen task.
  - We could start working on C++ import.  C++ import is a huge task, but we 
don't really need most of it for this.

John.

> 
>> Presumably, binding to Python is going to require some compiler 
>> effort—defining how it is that Python objects are 
>> initialized/copied/moved/destroyed seems like a reasonable part of that 
>> effort.
> 
> Actually no.  If we add these three proposals, there is no other python (or 
> perl, etc…) specific support needed.  It is all implementable in the overlay.
> 
>>> // Magic, allows anyobject-like member lookup on a type when lookup 
>>> otherwise fails.
>>> protocol DynamicMemberLookupable {
>>>   associatedtype MemberLookupResultType
>>>   func dynamicMemberLookup(_ : String) -> MemberLookupResultType
>>> }
>> 
>> AnyObject lookup looks for an actual declaration on any type anywhere. One 
>> could extend that mechanism to, say, return all Python methods and assume 
>> that you can call any Python method with any PythonObject instance. 
>> AnyObject lookup is fairly unprincipled as a language feature, because 
>> there’s no natural scope in which to perform name lookup, and involves hacks 
>> at many levels that don’t always work (e.g., AnyObject lookup… sometimes… 
>> fails across multiple source files for hard-to-explain reasons). You’re 
>> taking on that brokenness if you expand AnyObject lookup to another 
>> ecosystem.
> 
> Yeah, sorry, that’s not what I meant:
> 
>> Although it doesn’t really seem like AnyObject lookup is the thing you’re 
>> asking for here. It seems more like you want dynamicMemberLookup(_:) to 
>> capture “self” and the method name, and then be a callable thing as below…
> 
> That’s what I meant :-).
> 
> A type that implements this magic protocol would never fail name lookup: 
> “foo.bar” would always fall back to calling: foo.dynamicMemberLookup(“bar")
> 
> it’s simple and more predictable than AnyObject, it also matches what dynamic 
> languages like Python needs.
> 
>>> // Magic, allows “overloaded/sugared postfix ()”.
>>> protocol CustomCallable {
>>>  func call( …)
>>> }
>>> 
>>> The only tricky thing about this is the call part of things.  At least in 
>>> the case of python, we want something like this:
>>> 
>>>   foo.bar(1, 2, a: x, b: y)
>>> 
>>> to turn into:
>>>  foo.dynamicMemberLookup(“bar”).call(1, 2, kwargs: [“a”:x, “b”:y])
>>> 
>>> We don’t want this to be a memberlookup of a value that has “bar” as a 
>>> basename and “a:” and “b:” as parameter labels.
>> 
>> Well, I think the MemberLookupResult is going to get the name “bar”, 
>> argument labels “_:_:a:b:”, and arguments “1”, “2”, “x”, “y”, because that’s 
>> the Swift model of argument labels. It can then reshuffle them however it 
>> needs to for the underlying interaction with the Python interpreter.
>> 
>> There are definite design trade-offs here. With AnyObject lookup, it’s a 
>> known-broken feature but because it depends on synthesized Swift method 
>> declarations, it’ll behave mostly the same way as other Swift method 
>> declarations—static overloading, known (albeit weak) type signatures, etc. 
>> But, it might require more of Python’s model to be grafted onto those method 
>> declarations. With dynamic member lookup, you’re throwing away all type 
>> safety (even for a motivated Python developer who might be willing to 
>> annotate APIs with types) and creating a general language mechanism for 
>> doing that.
> 
> Right, something like this could definitely work, but keep in mind that the 
> Swift compiler knows nothing about Python declarations.  
> 
> Perhaps the most straight-forward thing would be to support:
> 
> protocol CustomCallable {
>  func call(…arg list as array and kw args...)
>  func callMember(_ : String, …otherstuffabove...)
> }
> 
> Given this, the compiler could map:
> 
> pythonThing(42)    -> pythonThing.call([42])
> pythonThing.method(a: 42)   -> pythonThing.callMember(“method”, kwargs: [“a”: 
> 42])
> 
> This is the simplest way to map the Swift semantics (where kw args are part 
> of compound lookups) into the Python world.
> 
> -Chris
> 
> 

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

Reply via email to