Trailing closures will be supported by this.
-Chris
Trailing closures will be supported by this.
-Chris
Yes. Each of the T* types are arbitrary as mentioned in the proposal. It is possible (though potentially unwise) for an implementation to make them differ. The compiler will deterministically select a candidate based on the "closest match" rule based on syntax, not considering the types.
Great catch, fixed, thanks!
-Chris
I don't have a strong opinion about it, but the rationale was elimination of a redundant state. It isn't possible to pass a "present but empty" keyword, which modeling this as an optional string would allow.
-Chris
Turns out it almost is
:
func f( lol: String) {
print(lol)
}
f(:"ok")
btt: That sounds reasonable. I was thinking about allocations, but these strings would probably all be StaticString and interned together anyway.
func dynamicMethodCall(baseName: S1, arguments: [T5]) -> T6
func dynamicMethodCall(baseName: S2, keywordArguments: [(S3, T7)]) -> T8
We write the
argumentsandkeywordArgumentsparameter as an array type, but these will actually be allowed to be any type that conforms to theExpressibleByArrayLiteralprotocol, where the element type has the specified constraints.
If the keywordArguments parameter was any type that conforms to the ExpressibleByDictionaryLiteral protocol, then an implementation might be able to use:
the DictionaryLiteral generic structure (which preserves the ordering);
or a custom type, such as the PyValue structure (which doesn't preserve the ordering).
func dynamicMethodCall(baseName: PyValue, arguments: PyValue) -> PyValue
func dynamicMethodCall(baseName: PyValue, keywordArguments: PyValue) -> PyValue
APIs could be added to the DictionaryLiteral generic structure, to make it easier to implement @dynamicCallable methods. I'm not sure if it would be possible to do this for an array of tuples.
At first I thought a subscript would be a better fit for the dynamicMethodCall because to me this is just an extension to dynamicMemberLookup so it would be possible to satisfy both protocols with just one getter subscript. (I guess the limitation here would be that a lambda could not be assigned to unless a setter was implemented as part of the dynamicMemberLookup requirement).
Then I stated to think about the limitation of subscripts not being able to throw so this would be a non starter issue for that to happen. I no longer think it should be a subscript.
Hi Chris. Thank you for all of the awesome work you’re doing with the Swift for Tensor Flow project. There is a lot of exciting stuff happening on your new team!
Now that dynamic member lookup has been accepted it makes sense to complete the dynamic language interop story. I’m content to let members of the community who are more interested in this area to comment on most of the details.
There is however one area in particular that I do have a reasonably strong opinion. If we are going to add support for callable types to Swift I believe this should include support for the static argument lists (i.e. “statically callable” types) that you mentioned as a future direction. Ideally this would include the ability to specify “call” requirements in protocols (and eventually the ability to conform function types to protocols with a “call” requirement that matches their signature).
I understand the argument of pragmatism focused on dynamic language interop for adding support for dynamically callable types before adding support for statically callable types. It just feels extremely unfortunate to me. It feels like native Swift types and native Swift code which would benefit from statically checked callable types is being asked to take a back seat for the sake of progamatism.
What would it take to support both static and dynamic callable types in the Swift 5 timeframe? I would feel much better about your proposal if a more complete solution for callable types was on the table in the near term.
dynamicMemberLookup does not support static members. Why should dynamicCallable support static callable? I believe that this proposal should be limited to supporting only initializes and methods. I am not sure how initializers would interact with full static callable feature but I do not think this proposal should be held back because it doesn't have a full callable feature unless that is necessary for dynamic languages interop.
Hmm, I mean in addition to the regular arguments. For example to represent Ruby:
myObj.foo(a, b, c) { |f| stuff(f) }
...in Swift as written:
myRubyValue.foo(a, b, c) { f in stuff(f) }
...giving Swift as compiled, straw man:
myRubyValue.dynamicCall(basename: "foo", arguments: [a, b, c], closure: { f in stuff(f) })
The point of this is to preserve normal Swift + Ruby syntax, vs. having to write an explicit adapter to convert the closure to the currency type (without ExpressibleAsFunctionLiteral) as in:
myRubyValue.foo(a, b, c, RubyBlockValue({ f in stuff(f) }))
What would that even mean? Swift certainly has had support for static member lookup since the beginning. This is not a valid comparison. That proposal introduced a dynamic equivalent to a pre-existing static capability. The new proposal introduces a dynamic capability for which there is currently not a static equivalent.
Is that worth the complexity? Bear in mind those people will get no code-completion or anything, so they will basically have to know exactly how to call these APIs anyway. Are users really going to start mixing in keyword arguments around a bunch of JS code without this?
Also, exactly what happens to keywords might be an implementation decision. A JS implementation might allow them so your Swift code looks nicer, but simply ignore them when bridging to JS.
Did you consider other solutions, like “#error” inside an “@inlineable” function which can be evaluated statically?
Like I said before, the functionality is fine but I feel the implementation is awkward. Especially since these requirements are not really visible in a protocol declaration or anything.
This doesn't work because subscripts can not be static
@dynamicMemberLookup
struct MyType
{
static subscript(dynamicMember member: String) -> String {
print(member)
return ""
}
}
MyType.somethingStatic
It is not at all clear to me how this response relates to my post. I didn't say anything at all about subscripts. I think you might be confused though: in this context "static" does not mean static. It means "callable with a signature that is statically verified at usage sites".
How does this relate to variadic parameters?
Now there is func f(_ arguments: Value...) and dynamicCall(arguments: [Value]) which both get the function arguments as an array.
What about splitting this proposal into multiple orthogonal features?
@callable. This would simply redirect the call to a member of that object (I'll use call() in the examples below). Together with the already existing variadic arguments, this could already handle the func dynamicCall(arguments: [T1]) -> T2 case (as func call(_ arguments: T1...) -> T2).# (or @, \, *, whatever...) instead of a keyword to mean: this argument can be written with a keyword, please pass a pair of (keyword, value) as the argument to the function. With something like that, the func dynamicCall(keywordArguments: [(String, T3)]) -> T4 case could be written as func call(# keywordArguments: (String, T3)...) -> T4.dynamicMethodCall() could be adapted to also use the new function call syntax: func dynamicMethodCall(baseName: S1, _ arguments: T5...) -> T6 and func dynamicMethodCall(baseName: S2, # keywordArguments: (S3, T7)...]) -> T8. Note that I think it makes sense to keep the dynamic in the name here, as the baseName argument is handled very similar to the dynamic member lookup feature. Maybe it makes sense to use a separate attribute for it, too. E.g. (@dynamicMethodCallable).Does that make sense?
Great point, I've revised the proposal to use ExpressibleByDictionaryLiteral, thank you!
-Chris
I considered this, but decided not to do it for a couple of reasons. First, I'm not aware of any strong motivating use cases for the static callable version of this. Such a feature should stand on its own, and justify its own complexity. Second, because there are no motivating use cases, it is difficult to design a feature and ensure that something isn't missed. Third, the static callable feature becomes particularly useful and import when we have generics features that will hopefully come later (like variadics).
That said, I agree with you that it is a foreseeable and perhaps likely direction for us to add something static callable. Because of that, I think it is important to design this feature with the idea that we'll eventually get there, so we don't paint ourselves into a corner or end up with something that doesn't fit well together. This is precisely why I suggested that it could make sense to name the attribute @callable instead of @dynamicCallable in the future directions section.
Do you have an opinion about that specifically? Are you interested in defining and driving a static callable proposal? If so, I'm happy to collaborate and make sure the two proposals mesh well together.
-Chris
With this proposal, the closure would naturally be compiled into:
myRubyValue.dynamicCall(basename: "foo", arguments: [a, b, c, { f in stuff(f) }])
It would be possible to further complicate the proposal by adding special support for trailing closures separated from the argument list, but I don't think we should do that proactively. Such an extension fits with the design direction very simply, and can be done when and if someone is actually interested in driving a Ruby binding in practice.
-Chris
Hi Karl,
The pro's and con's of using a formal protocol vs an attribute for these things was extensively discussed in the SE-0195 review cycle. The community and core team came to prefer an attribute (which has compiler enforced requirements) specifically because these features do not fit into protocols and generics.
For specific use cases, it is absolutely possible to define a protocol and use this attribute on it. That approach is what allows the feature to integrate properly with generics etc.
-Chris
This proposal is naturally variadic: whatever parameters you pass get sent through an array of elements to the implementation. Are you reacting to a hypothetical static version of this proposal?
I welcome other design proposals, but I'm not sure what problem you're solving here. What is the motivation for your approach? It seems much more complicated than my proposal, including e.g. new language syntax and call parameter concepts.
If you'd like to propose a different approach, can you please spell it out a little bit more and explain the pros/cons of your approach vs this one?
-Chris
Hi Chris,
Well, actually I tried to reduce complexity ;-)
It basically started with the observation that we may not need another way to introduce variadic arguments. We already have one and we could reuse that for the first form of dynamicCall.
So my idea is not motivated by a new use-case, it just tries to reuse existing mechanisms.
In order to call Python functions with keywords, we need something like variadic arguments, so I tried to find an approach which is as close to the existing syntax as possible.
Such a decomposition would allow:
In order to bridge to Python functions, both of these would have to be combined.
The advantage would of course be that these features could be used independently. But you are right that without a concrete use-case, it's difficult to rate the usefulness of these new features. I'll have to think about that some more.
Others have expressed the desire to have static callable objects; it would really be interesting to see some concrete examples where it would be useful.