[Pitch #3] Introduce user-defined dynamically "callable" types

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.


1 Like

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.


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.


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?


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:

  • callable objects, with a function signature and type checking just like normal functions;
  • functions with variadic keyword arguments.

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.


Well I guess that's me; I wouldn't call my bindings library production ready yet but should get there soon.

As long as this proposal sets up the direction I don't mind adding the trailing closure part as a follow-on, if you prefer to concentrate on the fundamental parts for now (of which Ruby would make good use.)

1 Like

So the caveat here would probably be that, at this moment, the Ruby dynamicCall would have to take Any instead of, say, RubyObject as argument type, since function types can't conform to protocols, right?

We'd also probably have to specify the parameter and return types, even if the closure could conform to a protocol.

The more practical approach would probably be to give your RubyObject, or some type conforming to some protocol, an initializer taking a closure and use trailing closure syntax to call that, so your call would look something like this:

myRubyValue.foo(a, b, c, RubyObject { f in stuff(f) })
1 Like

Ah yes, that is a good point. I believe there is a long term desire to allow non-nominal types like functions and tuples to conform to protocols. I'm not sure how that will work out in practice though.



I'm really sorry, but I still don't understand what you mean. What "another way" to represent variadic arguments are you referring to?

I can sort of see what you mean in that variadics in Swift implicitly turns the ... into an array of elements, and this is sort of like this. OTOH, what this proposal is really doing is packaging-up/boxing an entire argument list into one value, which isn't something you can do with variadics and definitely not with keyword argument labels.

I tend to see this as a very different thing and for a very different purpose than the problem that variadics solve.


I've seen APIs which simulate argument labels, by taking an object literal.

// JavaScript

function example(args) {
  var first = args.first || 0 // default is zero.
  // etc.

example({first: 1, second: 2, third: 3})

So a JavaScript bridge might want to support both styles (arguments and keywordArguments).

The revised proposal contains:

We write the arguments and keywordArguments parameter as an dictionary type, but these will actually be allowed to be any type that conforms to the ExpressibleByDictionaryLiteral protocol (which is inclusive of Dictinary, DictionaryLiteral, and other custom types), where the element type has the specified constraints.

But should the arguments still take an array literal?

We write the arguments parameter as an array type, but these will actually be allowed to be any type that conforms to the ExpressibleByArrayLiteral protocol, where the element type has the specified constraints.

We write the keywordArguments parameter as an dictionary type, but these will actually be allowed to be any type that conforms to the ExpressibleByDictionaryLiteral protocol (which is inclusive of Dictionary, DictionaryLiteral, and other custom types), where the element type has the specified constraints.

Agreed, that's the workaround I mentioned somewhere above. It would be more natural for both Swift + Ruby programmers though not to have the extra text.

Explicit modelling of the trailing closure in the dynamicCall implementation also provides further compile-time checking + user convenience in the spirit of the pitch: in Ruby the trailing closure has different syntactic meaning (Ruby 'block') to a closure passed as a method argument.

So although with the workaround we could write:

myRubyValue.foo(a, b, c, RbBlock { f in stuff(f) })

...with a/b/c/RbBlock conforming to RbObjectConvertible, the dynamicCall implementation has to check at run-time whether the last argument is actually an RbBlock to figure whether it is a method argument or a block (trailing closure). And validate the non-last arguments to make sure they were not misplaced RbBlocks.

All doable of course!

I think it's unlikely that a type would want to support both static and dynamic callable. With that in mind, I think separate attributes make the most sense. If somebody really wanted to expose both static and dynamic calls they could just add both @callable and @dynamicCallable to the type.

I'm not able to work on implementation but I would be happy to collaborate with anyone interested this feature. I'll spend some time thinking about this (especially motivation) and start a pitch thread in the next week or two.

I noticed that this proposal does not support both positional and keyword arguments in the same call. Some dynamic languages (such as Ruby) do support this. Is there a reason this proposal doesn't support mixing them or was that an oversight?

1 Like

The implication (or at least my reading) of para 3 under 'Ambiguity resolution' (e: combined with some comments upthread from Lukas + Chris) is that it does support that, that if there are any keywords given then the 'keywords' form is used and positional arguments come in with empty keyword strings.

(This doesn't support compile-time checking of Ruby's "positional arguments must precede keyword arguments" but that is fairly baroque...)

1 Like

The current version Swift seems to trap when initializing a type conforming ExpressibleByDictionaryLiteral with multiple equal keys which would be required to use a dynamicCall with multiple arguments (with an empty string key) and keyword arguments.

Paragraph 3 says:

Similarly, if a type implements both the keywordArguments and the arguments form, the compiler will use the arguments form for call sites that lack keyword arguments, and use the more general form for call sites that do have keyword arguments.

It isn't clear from this that keyword arguments are allowed for some, but not all arguments. I certainly don't see anything that indicates that the positional arguments come with any specific keyword string (such as empty). I think this needs to be spelled out in more detail if it is supported, or clearly stated as a limitation if it is not supported.

The proposal is using DictionaryLiteral which allows duplicate keys. Its is a weird type. Further discussion about it.

func justPrinting<T>( _ input: DictionaryLiteral<String, T>)

justPrinting(["": 1, "": 2, "": 3]) // DictionaryLiteral<String, Int>(_elements: [("", 1), ("", 2), ("", 3) ])

Oh, interesting :thinking:

Well, the @dynamicCallable feature captures a variable amount of arguments and provides them as one argument to a function, just like the existing ... syntax. So we'd have two very similar features next to each other.

Obviously, variadics at the moment cannot capture keyword argument labels. But when there are no keyword arguments, they can already perfectly package the entire list, right? Or is there something I missed?
That's why I thought adding keyword support to our existing variadics feature might be simpler and also more powerful than introducing a new feature which is just available for structs/classes/etc. but not available for normal functions.
If that is not the case or if the added flexibility is not needed, then so be it.

1 Like