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