Object that are functions too, Amazing! I wanted that in Javascript for a while!

On Nov 10, 2017, 1:04 PM -0500, Joe Groff via swift-evolution 
<swift-evolution@swift.org>, wrote:
> I don't like the idea of some calls having wildly different semantics from 
> others; it's difficult enough to tell what exactly a call might be doing 
> already. Since we also lack the more obvious static "Callable" protocol idea 
> to give even well-typed call syntax to user-defined types, this also seems 
> like it'd be easily abused for that purpose too.
>
> I think a much better general solution to the problem of "make dynamic 
> systems interact with type systems" is something like F#'s type providers 
> which lets you write your own importers that look at dynamic information from 
> a database, dynamic language VM, or some other system and generate type 
> information usable by the compiler. Integration at the importer level could 
> let you produce more well-typed Swift declarations by looking at the runtime 
> information you get by importing a Python module.
>
> -Joe
>
> > On Nov 10, 2017, at 9:37 AM, Chris Lattner via swift-evolution 
> > <swift-evolution@swift.org> 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
> > swift-evolution@swift.org
> > https://lists.swift.org/mailman/listinfo/swift-evolution
>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution@swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to