The general class of functionality this provides is important, but I have some misgivings about this design.
Swift is moving towards a model where argument labels are part of the name of a function, not part of the function itself. We've discussed this direction many times in the past—for instance, when we removed argument labels from function types and made associated value labels part of a case's name.
dynamicCall(withKeywordArguments:)
ignores this direction in the language's evolution. The semantics of these calls are fundamentally quite different from a normal Swift function. I worry that, as we try to evolve the AST to improve the extremely messy way we currently handle Swift calls, we may hit limits imposed by this proposal. I don't want to end up in a situation where we're handling Swift code worse in order to handle Python code better.
My other concern about this design is that it is laser-focused on Python. The proposal gestures at how one feature might be used in Javascript, but it's not ideal for that. It doesn't handle Smalltalk and Ruby at all (and IMHO, misses the main challenge of Ruby—it doesn't need to access argument labels early, it needs to handle ambiguity between calls and property accesses). It doesn't help us with any problems outside language bridging.
On a bikeshedding front, I worry that the distinction between "dynamic callable" and "dynamic function" (as in the dynamic
keyword) may be too subtle, and that if the @dynamicMemberCallable
feature suggested in "future directions" is later adopted, the distinction between @dynamicCallable
, @dynamicMemberCallable
, and @dynamicMemberLookup
will also be too subtle.
I think this functionality should be implemented as two orthogonal features:
-
Add a variadic list of argument labels to the lookup done by @dynamicMemberLookup
. This would let you look up a member whose name includes argument labels.
-
Add a way to use plain function call syntax on a non-function type, along the lines of C++'s operator ()
. Python would declare one of these with a variadic list of arguments, but the feature would be able to serve many other use cases, too.
This design would be worse for Python, but better for everything else. It would slot more readily into Swift's semantics and it would cover many more use cases. The downsides are that it makes the Python bridge's job harder (it would need to save the labels seen in the @dynamicMemberLookup
call inside the instance it returned), and that a user would not be able to assign a PyObject
representing a function to a variable and then call it with keyword arguments using the ordinary function syntax (which they wouldn't be able to do with a Swift closure, either). I think a little bridge awkwardness is worth having a more flexible, semantically simpler feature.
I think good dynamic bridging is a great thing to add to the language, but I don't think this particular design is right for Swift. It is too limiting and too limited. I urge the core team to reject SE-0216 and ask for a different solution to the same problem.