Can this be unified with @dynamicCallable somehow? What if a type is both static and dynamic callable, which one takes precedence?
You should also keep in mind that adding a new declaration kind has a very high implementation cost, and it would be preferable to work this in via a subscript or method instead.
This implicit conversion might be a bit tricky because function values have reference semantics. So would converting a value type to a function value give you a mutable box?
This would be fine with me in general. The only scenario I can imagine finding it surprising is if the callable method was mutating. By making this a new kind of declaration I think it sidesteps this issue (unless we have a reason to allow mutating call).
The pitch has a section on types that both have call members and are marked with @dynamicCallable:
Basically, call members take precedence over @dynamicCallable methods.
I don't think exactly unifying call members and @dynamicCallable behavior is possible because @dynamicCallable provides special sugar for argument labels:
// With `@dynamicCallable.
np.random.randint(-10, 10, dtype: np.float)
// With `call` methods. The empty strings are killer.
np.random.randint(["": -10, "": 10, "dtype": np.float])
Using a nameless func or func _(...) is possible, but to me they don't convey "callable method" as clearly as an explicit call keyword.
Also, we want to support direct references to callable methods via foo.call. This makes more sense when callable methods are declared with the call keyword, and is consistent with init declarations/direct references (e.g. foo.init).
I do like the clever use of self, and I think it can be applied to subscripts too, showing the operator.
func self[_ x: Int] -> Int { // subscript
return base + x
}
And you can directly reference them via x.self(_:) and x.self[_:].
In any case, I believe the call declaration syntax and the subscript declaration syntax need to be consistent. The current subscript declaration syntax is already being used everywhere—changing this has significantly higher cost.
I like the feature. The main ugly parts are breaking source compatibility and stealing the short and probably useful function name “call”. Is there a reason why you couldn't allow both call and func call with only the former being “callable”? You could disallow name clashes in the usual way, and possibly warn if the two are mixed on the same type. I suppose subscript doesn't work like that but I'm not sure why. Is it ambiguous or just confusing?
It's not 100% ambiguous but anything like this makes it harder to recover when parsing erroneous or incomplete code. As a further example, this would no longer be valid:
func call(_ fn: () -> Void) {}
call() {}
because that call() {} now looks like a declaration rather than a use! (Albeit a misplaced one, since it's not on a type.) You really want to ban such a thing.
I'm still not convinced that the new declaration kind is the way to go. Another potential reason I thought of today (besides the source compat problem) is chaining:
struct Addition {
func __call__(_ a: Int, _ b: Int) -> Int {
return a + b
}
}
struct Identified<Operation> {
var __call__: Operation
var identifier: String
init(_ op: Operation, identifier: String) {
self.__call__ = op
self.identifier = identifier
}
}
let add = Identified(Addition(), identifier: "+")
print(add(1, 2))
But maybe this is getting too clever. You can always get the same effect with a closure or explicit wrapping call function.
(I'm not actually proposing __call__.)
I'm also a little concerned about type checker performance because we have to assume that basically anything is callable now and prove that it's not, but that's probably okay in practice because type expressions already were.
I've seen some positive feedback on the call syntax, so @dan-zheng and I will continue to implement the feature as proposed! Some of the concerns around the proposed syntax can be discussed in a proposal review once our implementation is done.
A quick skim of Github shows a fair amount of Swift code using “call” as a method name. Some of these uses would probably benefit from being ported to be callable, e.g.
A lot of the latter use seemed to be in beginner code (which is underrepresented on Github), because this is a short and natural method name. This is unfortunate, because it's not a great experience when learning to immediately hit a keyword collision and have to rename your method or call it with backticks everywhere. This is in contrast to subscript, which seems like a much less likely collision (unless you're in the typesetting domain). Is there a longer/different keyword that would work here instead?
invoke(): It is much less obvious to me that this implies the call syntax.
apply(): There could be even more collisions with this one. Also Swift doesn't officially use "function application" to describe function calls.
parentheses()
If there's consensus that source breakage or the ambiguity call brings is not acceptable, then I'll explore the path of changing both subscript(...) and call(...) to self[...] and self(...) respectively.
One obvious alternative, given that dynamic callable uses dynamicallyCall methods, is staticallyCall. This collides with the other meaning of static for functions though, so maybe someone can think of a better adverb.
Ditto. I think that "static" is too confusing and therefore didn't consider it as an alternative. I also believe any adverb would make this first-class syntax feature sound less first-class.
I suppose I don't find the motivating examples very compelling.
Wouldn't this example be better served by having an apply operator rather than static syntax (naming would go a long way too)?
input <> conv <> maxPool <> flatten <> dense
It seems like proper naming plus an operator would provide the superior readability. Just so I understand, this doesn't add any additional capability to the language, it just provides "sugar" for those cases where we want to call a method but not pay the price of naming or calling the method?