In Java you can have annotation processors, user supplied compiler extensions, 
that are extensively used for making frameworks easier to use and for general 
interfacing to ‘foreign stuff’.

Would a limited form of user supplied compiler extension be an alternative, an 
import processor. Special import syntax, import beginning with ‘Foreign’, would 
be recognised by the compiler and a user supplied import processor would be 
called, in the case of the example for Python. The import processor would 
generate the glue code. Below is a basic example to give a feel for what I am 
proposing.

(Syntax, names, etc. in code below just the 1st that came into my head!)

  — Howard.

======================================================

import Foreign.Python.Foo // Special import tag `Foreign` calls a user supplied 
compiler extension that parses the imported Python and generates something 
along these lines (its up to the importer the exact code generated!):

protocol _ForeignPythonCallable {
    func call(method: Any, arguments: Any...) throws -> Any
}

extension _ForeignPythonCallable {
    func call(method: Any, arguments: Any...) throws -> Any {
        return "method: \(method), arguments: \(arguments)" // Method lookup 
and calling code for Python.
    }
}

class Foo: _ForeignPythonCallable {
    // Could override `call` if more efficient than complete general lookup was 
possible.
    enum _ForeignPythonMethodNames { // This might be a C struct so that it 
matches perfectly what the python interpreter is expecting.
        case bar
    }
    enum _ForeignPythonMethodBarArguments { // This might be a C struct so that 
it matches perfectly what the python interpreter is expecting.
        case x(Any)
        case noName1(Any)
        case y(Any)
    }
    func bar(x: Any, _ noName1: Any, y: Any) -> Any {
        do {
            return try call(method: _ForeignPythonMethodNames.bar, arguments: 
_ForeignPythonMethodBarArguments.x(x), 
_ForeignPythonMethodBarArguments.noName1(noName1), 
_ForeignPythonMethodBarArguments.y(y))
        } catch {
            fatalError("Method `bar` does not throw, therefore Python importer 
bug.")
        }
    }
}

// Then the consumer of `Foo` uses as per normal Swift:

let foo = Foo()
foo.bar(x: 1, 23, y: 17)

class TypedFoo: Foo { // For many dynamic languages this wouldn't be possible 
to automatically generate via the import, therefore would have to be hand 
written.
    func bar(x: Int, _ noName1: Int, y: Int) -> String {
        return super.bar(x: x, noName1, y: y) as! String
    }
}
let typedFoo = TypedFoo()
typedFoo.bar(x: 1, 23, y: 17)


> On 12 Nov 2017, at 11:15 am, Andrew Bennett via swift-evolution 
> <[email protected]> wrote:
> 
> HI, this proposal looks really interesting!
> 
> I have a few questions:
> 
> Clarity on the proposal's intent
> Nice cheap bridges, or lowering barriers to bridging?
> 
> I can see this providing a nice quick interface to Python from Swift, but I'm 
> not sure if the exposed interface will be very Swifty (you probably have a 
> much better idea of what is Swifty ;) than I do though). It seems you want it 
> to be possible for everything to be dynamically exposed, I've used similar 
> with Lua's meta methods, and I found it to be very powerful, you could 
> basically implement inheritance in the language, which wasn't necessarily a 
> good thing in retrospect.
> 
> Is it common for the the argument labels in other languages to be open ended, 
> or are labels typically finite? If the answer is finite, why not use a Swift 
> method as the wrapper?
> Do you want duck typing, and would it be better to expose this via a protocol?
> 
> It seems like in almost every case you could do something like this:
> 
> func myMethod<X: PythonConvertible & CanQuack, Y: PythonConvertible>(a: X? = 
> nil, b: Y) {
>     pythonBridge.call("myMethod", arguments: ["a": X, "b": Y])
> }
> 
> It might be good to add some use-cases (a popular Python library perhaps) to 
> the proposal where this type of bridge would be insufficient :).
> 
> It seems like this proposal pushes the responsibility of Swifty-ness and 
> type-safety to the caller. At some point you'll have to write a type-safe 
> bridging layer, or write your entire program in non-Swifty code ("The most 
> obvious way to write code should also behave in a safe manner"). Is the main 
> goal to lower the barrier to Python and other dynamic languages? or is it to 
> provide a cheap nice Swifty bridge? I have the above concerns about the 
> latter.
> 
> Alternative sugar
> 
> Ruby has Keyword Arguments for similar sugar:
> 
> def foo(regular, hash={})
>     puts "the hash: #{hash}"
> 
> 
> I'm sure you're aware of it, but I'll explain for completeness, any trailing 
> argument labels are stored and passed as a hash:
> 
> foo(regular, bar: "hello", bas: 123) # outputs 'the hash: [bar: "hello", bas: 
> 123]'
> Have you considered an alternative like this? For example:
> 
> func myMethod(regular: Int, store: @argcapture [String: PythonConvertible]) 
> -> PythonConvertible
> 
> I'm sure you have good reasons, it might make the implementation bleed out 
> into other parts of the codebase. It would be good to include it in the 
> proposal alternatives section though. At the moment most of the 
> "alternatives" in the proposal just seem to be extensions to the same 
> solution :)
> 
> Clarity
> Perhaps just that things are more clear to me now
> 
> If my extrapolation is correct a user will implement a single type that will 
> allow a subset of a good portion of another language to be exposed (object 
> method and property bridging). I'm guessing that the dynamic member proposal 
> you're planning will not work with methods, it will require a property, I 
> think this helps explain some of the motivations. It might be nice to have a 
> more complete example that includes dynamic members. I didn't find it clear 
> from the proposal that it would only be necessary to implement this protocol 
> once per language.
> 
> Thanks for considering my questions,
> Andrew Bennett
> 
> 
>> On Sat, Nov 11, 2017 at 4:37 AM, Chris Lattner via swift-evolution 
>> <[email protected]> wrote:
>> Hello all,
>> 
>> I have a couple of proposals cooking in a quest to make Swift interoperate 
>> with dynamically typed languages like Python better.  Instead of baking in 
>> hard coded support for one language or the other, I’m preferring to add a 
>> few small but general purpose capabilities to Swift.  This is the first, 
>> which allows a Swift type to become “callable”.
>> 
>> The proposal is here:
>> https://gist.github.com/lattner/a6257f425f55fe39fd6ac7a2354d693d
>> 
>> I’ve also attached a snapshot below, but it will drift out of date as the 
>> proposal is refined.  Feedback and thoughts are appreciated, thanks!
>> 
>> -Chris
>> 
>> 
>> 
>> 
>> 
>> Introduce user-defined dynamically "callable" types
>> Proposal: SE-NNNN
>> Author: Chris Lattner
>> Review Manager: TBD
>> Status: Awaiting implementation
>> Introduction
>> 
>> This proposal introduces a new DynamicCallable protocol to the standard 
>> library. Types that conform to it are "callable" with the function call 
>> syntax. It is simple syntactic sugar which allows the user to write:
>> 
>>     a = someValue(keyword1: 42, "foo", keyword2: 19)
>> and have it be interpreted by the compiler as:
>> 
>>   a = someValue.dynamicCall(arguments: [
>>     ("keyword1", 42), ("", "foo"), ("keyword2", 19)
>>   ])
>> Other languages have analogous features (e.g. Python "callables"), but the 
>> primary motivation of this proposal is to allow elegant and natural 
>> interoperation with dynamic languages in Swift.
>> 
>> Swift-evolution thread: Discussion thread topic for that proposal
>> 
>> Motivation
>> 
>> Swift is well known for being exceptional at interworking with existing C 
>> and Objective-C APIs, but its support for calling APIs written in scripting 
>> langauges like Python, Perl, and Ruby is quite lacking. These languages 
>> provide an extremely dynamic programming model where almost everything is 
>> discovered at runtime.
>> 
>> Through the introduction of this proposal, and the related 
>> DynamicMemberLookupProtocol proposal, we seek to fix this problem. We 
>> believe we can make many common APIs feel very natural to use directly from 
>> Swift without all the complexity of implementing something like the Clang 
>> importer. For example, consider this Python code:
>> 
>> class Dog:
>>     def __init__(self, name):
>>         self.name = name
>>         self.tricks = []    # creates a new empty list for each dog
>>         
>>     def add_trick(self, trick):
>>         self.tricks.append(trick)
>> we would like to be able to use this from Swift like this (the comments show 
>> the corresponding syntax you would use in Python):
>> 
>>   // import DogModule
>>   // import DogModule.Dog as Dog    // an alternate
>>   let Dog = Python.import(“DogModule.Dog")
>> 
>>   // dog = Dog("Brianna")
>>   let dog = Dog("Brianna")
>> 
>>   // dog.add_trick("Roll over")
>>   dog.add_trick("Roll over")
>> 
>>   // dog2 = Dog("Kaylee").add_trick("snore")
>>   let dog2 = Dog("Kaylee").add_trick("snore")
>> Of course, this would also apply to standard Python APIs as well. Here is an 
>> example working with the Python pickleAPI and the builtin Python function 
>> open:
>> 
>>   // import pickle
>>   let pickle = Python.import("pickle")
>> 
>>   // file = open(filename)
>>   let file = Python.open(filename)
>> 
>>   // blob = file.read()
>>   let blob = file.read()
>> 
>>   // result = pickle.loads(blob)
>>   let result = pickle.loads(blob)
>> This can all be expressed today as library functionality written in Swift, 
>> but without this proposal, the code required is unnecessarily verbose and 
>> gross. Without it (but with the related dynamic member lookup proposal) the 
>> code would have a method name (like call) all over the code:
>> 
>>   // import pickle
>>   let pickle = Python.import("pickle")  // normal method in Swift, no change.
>> 
>>   // file = open(filename)
>>   let file = Python.open.call(filename)
>> 
>>   // blob = file.read()
>>   let blob = file.read.call()
>> 
>>   // result = pickle.loads(blob)
>>   let result = pickle.loads.call(blob)
>> 
>>   // dog2 = Dog("Kaylee").add_trick("snore")
>>   let dog2 = Dog.call("Kaylee").add_trick.call("snore")
>> While this is a syntactic sugar proposal, we believe that this expands Swift 
>> to be usable in important new domains. This sort of capability is also 
>> highly precedented in other languages, and is a generally useful language 
>> feature that could be used for other purposes as well.
>> 
>> Proposed solution
>> 
>> We propose introducing this protocol to the standard library:
>> 
>> protocol DynamicCallable {
>>   associatedtype DynamicCallableArgument
>>   associatedtype DynamicCallableResult
>> 
>>   func dynamicCall(arguments: [(String, DynamicCallableArgument)]) throws -> 
>> DynamicCallableResult
>> }
>> It also extends the language such that function call syntax - when applied 
>> to a value of DynamicCallable type - is accepted and transformed into a call 
>> to the dynamicCall member. The dynamicCall method takes a list of tuples: 
>> the first element is the keyword label (or an empty string if absent) and 
>> the second value is the formal parameter specified at the call site.
>> 
>> Before this proposal, the Swift language has two types that participate in 
>> call syntax: functions and metatypes (for initialization). Neither of those 
>> may conform to protocols at the moment, so this introduces no possible 
>> ambiguity into the language.
>> 
>> It is worth noting that this does not introduce the ability to provide 
>> dynamicly callable static/class members. We don't believe that this is 
>> important given the goal of supporting dynamic languages like Python, but if 
>> there is a usecase discovered in the future, it could be explored as future 
>> work. Such future work should keep in mind that call syntax on metatypes is 
>> already meaningful, and that ambiguity would have to be resolved somehow.
>> 
>> Discussion
>> 
>> While the signature for dynamicCall is highly general we expect the most 
>> common use will be clients who are programming against concrete types that 
>> implement this proposal. One very nice aspect of this is that, as a result 
>> of Swift's existing subtyping mechanics, implementations of this type can 
>> choose whether they can actually throw an error or not. For example, 
>> consider this silly implementation:
>> 
>> struct ParameterSummer : DynamicCallable {
>>   func dynamicCall(arguments: [(String, Int)]) -> Int {
>>     return arguments.reduce(0) { $0+$1.1 }
>>   }
>> }
>> 
>> let x = ParameterSummer()
>> print(x(1, 7, 12))  // prints 20
>> Because ParameterSummer's implementation of dynamicCall does not throw, the 
>> call site is known not to throw either, so the print doesn't need to be 
>> marked with try.
>> 
>> Example Usage
>> 
>> A more realistic (and motivating) example comes from a prototype Python 
>> interop layer. While the concrete details of this use case are subject to 
>> change and not important for this proposal, it is perhaps useful to have a 
>> concrete example to see how this comes together.
>> 
>> That prototype currently has two types which model Python values, one of 
>> which handles Python exceptions and one of which does not. Their 
>> conformances would look like this, enabling the use cases described in the 
>> Motivation section above:
>> 
>> extension ThrowingPyRef: DynamicCallable {
>>   func dynamicCall(arguments: [(String, PythonConvertible)]) throws
>>       -> PythonConvertible {
>>     // Make sure state errors are not around.
>>     assert(PyErr_Occurred() == nil, "Python threw an error but wasn't 
>> handled")
>> 
>>     // Count how many keyword arguments are in the list.
>>     let numKeywords = arguments.reduce(0) {
>>       $0 + ($1.0.isEmpty ? 0 : 1)
>>     }
>> 
>>     let kwdict = numKeywords != 0 ? PyDict_New() : nil
>> 
>>     // Non-keyword arguments are passed as a tuple of values.
>>     let argTuple = PyTuple_New(arguments.count-numKeywords)!
>>     var nonKeywordIndex = 0
>>     for (keyword, argValue) in arguments {
>>       if keyword.isEmpty {
>>         PyTuple_SetItem(argTuple, nonKeywordIndex, argValue.toPython())
>>         nonKeywordIndex += 1
>>       } else {
>>         PyDict_SetItem(kwdict!, keyword.toPython(), argValue.toPython())
>>       }
>>     }
>> 
>>     // Python calls always return a non-null value when successful.  If the
>>     // Python function produces the equivalent of C "void", it returns the 
>> None
>>     // value.  A null result of PyObjectCall happens when there is an error,
>>     // like 'self' not being a Python callable.
>>     guard let resultPtr = PyObject_Call(state, argTuple, kwdict) else {
>>       throw PythonError.invalidCall(self)
>>     }
>> 
>>     let result = PyRef(owned: resultPtr)
>> 
>>     // Translate a Python exception into a Swift error if one was thrown.
>>     if let exception = PyErr_Occurred() {
>>       PyErr_Clear()
>>       throw PythonError.exception(PyRef(borrowed: exception))
>>     }
>> 
>>     return result
>>   }
>> }
>> 
>> extension PyRef: DynamicCallable {
>>   func dynamicCall(arguments: [(String, PythonConvertible)])
>>       -> PythonConvertible {
>>     // Same as above, but internally aborts instead of throwing Swift
>>     // errors.
>>   }
>> }
>> Source compatibility
>> 
>> This is a strictly additive proposal with no source breaking changes.
>> 
>> Effect on ABI stability
>> 
>> This is a strictly additive proposal with no ABI breaking changes.
>> 
>> Effect on API resilience
>> 
>> This has no impact on API resilience which is not already captured by other 
>> language features.
>> 
>> Alternatives considered
>> 
>> A few alternatives were considered:
>> 
>> Add ability to reject parameter labels
>> 
>> The implementation above does not allow an implementation to staticly reject 
>> argument labels. If this was important to add, we could add another protocol 
>> to model this, along the lines of:
>> 
>> /// A type conforming just to this protocol would not accept parameter
>> /// labels in its calls.
>> protocol DynamicCallable {
>>   associatedtype DynamicCallableArgument
>>   associatedtype DynamicCallableResult
>> 
>>   func dynamicCall(arguments: [DynamicCallableArgument]) throws -> 
>> DynamicCallableResult
>> }
>> 
>> /// A type conforming to this protocol does allow optional parameter
>> /// labels.
>> protocol DynamicCallableWithKeywordsToo : DynamicCallable {
>>   func dynamicCall(arguments: [(String, DynamicCallableArgument)]) throws -> 
>> DynamicCallableResult
>> }
>> This would allow a type to implement one or the other based on their 
>> capabilities. This proposal is going with a very simple design, but if there 
>> is demand for this, the author is happy to switch.
>> 
>> Staticly checking for exact signatures
>> 
>> This protocol does not allow a type to specify an exact signature for the 
>> callable - a specific number of parameters with specific types. If we went 
>> down that route, the best approach would be to introduce a new declaration 
>> kind (which would end up being very similar to get-only subscripts) since, 
>> in general, a type could want multiple concrete callable signatures, and 
>> those signatures should participate in overload resolution.
>> 
>> While such a feature could be interesting for some use cases, it is almost 
>> entirely orthogonal from this proposal: it addresses different use cases and 
>> does not solve the needs of this proposal. It does not address our needs 
>> because even a variadic callable declaration would not provide access to the 
>> keyword argument labels we need.
>> 
>> Direct language support for Python
>> 
>> We considered implementing something analogous to the Clang importer for 
>> Python, which would add a first class Python specific type(s) to Swift 
>> language or standard library. We rejected this option because it would be 
>> significantly more invasive in the compiler, would set the precedent for all 
>> other dynamic languages to get first class language support, and because 
>> that first class support doesn't substantially improve the experience of 
>> working with Python over existing Swift with a couple small "generally 
>> useful" extensions like this one.
>> 
>> Naming
>> 
>> The most fertile ground for bikeshedding is the naming of the protocol and 
>> the members. We welcome other ideas and suggestions for naming, but here are 
>> some thoughts on obvious options to consider:
>> 
>> We considered but rejected the name CustomCallable, because the existing 
>> Custom* protocols in the standard library (CustomStringConvertible, 
>> CustomReflectable, etc) provide a way to override and custom existing 
>> builtin abilities of Swift. In contrast, this feature grants a new 
>> capability to a type.
>> 
>> We considered but rejected a name like ExpressibleByCalling to fit with the 
>> ExpressibleBy* family of protocols (like ExpressibleByFloatLiteral, 
>> ExpressibleByStringLiteral, etc). This name family is specifically used by 
>> literal syntax, and calls are not literals. Additionally the type itself is 
>> not "expressible by calling" - instead, instances of the type may be called.
>> 
>> On member and associated type naming, we intentionally gave these long and 
>> verbose names so they stay out of the way of user code completion. The 
>> members of this protocol are really just compiler interoperability glue. If 
>> there was a Swift attribute to disable the members from showing up in code 
>> completion, we would use it (such an attribute would also be useful for the 
>> LiteralConvertible and other compiler magic protocols).
>> 
>> 
>> 
>> _______________________________________________
>> swift-evolution mailing list
>> [email protected]
>> https://lists.swift.org/mailman/listinfo/swift-evolution
>> 
> 
> _______________________________________________
> swift-evolution mailing list
> [email protected]
> https://lists.swift.org/mailman/listinfo/swift-evolution
_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to