but that defeats the beauty of Swift's callAsFunction feature, and it is possibly no longer worth it to me as the function author to choose to name the function callAsFunction if sometimes I'm going to have to explicitly reference that name in my business logic.
This feels similar to the issue where you can't bind a default argument when referencing a function as a value. For example,
func f(_: Int = 0) {}
let fn: () -> () = f // invalid
Or even that a metatype does not implicitly convert to a function type of its initializer.
Philosophically, do we expect that a things you can call convert to a function type, or that just functions themselves can be referenced as a "raw" function value without the other sugar that comes with a call expression? The latter is certainly much simpler to implement and reason about, but there's room for debate here.
Another piece of the puzzle to consider in the philosophical debate is that we allow a key path literal with base T and result U to convert to (T) -> U since it 'acts like' a function, but of course the actual application syntax for a keypath looks nothing like a (T) -> U function application.
It's worth mentioning that a bare metatype isn't allowed at all in expression position; most of the time when I try, it's because I wanted .self, not .init. In fact, there's currently an autofix for that:
struct Foo {}
let x = Foo // autofix: change to Foo.self
Usually, newly proposed syntax needs to have its semantics explained in the pitch (a simple ... obviously does not explain the vast complexity underlying the behavior of variadic generics), but in this case I think that we're all 100% clear on how my proposed spelling would behave and I basically didn't have to write any words at all explaining it, which I think is a evidence that there isn't going to be some other meaning in the future that we would wish to ascribe to my proposed spelling, and therefore it seems to me to be a safe change on that front. I don't know what parsing or other difficulties it could introduce.
Regarding the philosophical approach to this question, what do you guys think of conceptualizing callAsFunction as the "magical disappearing function name", the behavior of which is that everywhere where a function name can appear it can also be elided if the name happens to be exactly callAsFunction. The only exceptions would be implicit self method calls, where the callAsFunction can't be elided if self is elided, and global functions named callAsFunction which cannot elide the name either, (EDIT: a third exception is that (for now) static functions cannot elide callAsFunction, as pointed out by @bbrk24 ).
Would this change the current semantics of static methods named callAsFunction? Currently, any unqualified attempts to call them always refer to init, even if no init exists:
enum Foo {
static func callAsFunction() {}
}
let x = Foo() // error: 'Foo' cannot be constructed because it has no accessible initializers
Obviously, you can still say Foo.callAsFunction(). I just wanted to ensure your proposal wouldn't make the above legal, since you didn't list it as an exception.
Ah, good point. I have wanted this ability in the past, but in reality I think there are better ways (or at least we could implement better ways) to solve what I was going for. What this would allow is for expressions that look like initializations of one type to actually resolve to another type:
which is precisely why I wished it were possible, but again that's a big change and I'm not convinced that it's the right way, so I'll update my list of exceptions.
How could that ambiguity arise in Swift currently? Is it possible to define two methods with the same name, argument labels, argument types, and return type?
It doesn't affect parsing, but it introduces a new implicit conversion (from types with a callAsFunction method to function types) and we're generally wary of adding more implicit conversions since they can contribute to exponential type checking.
There is also a new possible ambiguity which might be mostly theoretical:
class C {}
class D: C {
func callAsFunction(...) {}
}
func f(_: C) {}
func f(_: () -> ()) {}
f(D()) // which conversion is better, the upcast or wrapping it in a closure?
Swift converts a key path literal to a function. It doesn't convert a key path value to a function.
func f(_: (Array<String>) -> Int) {}
f(\Array<String>.count) // ok
let kp: KeyPath<Array<String>, Int> = \Array<String>.count
f(kp) // 🛑 Cannot convert value of type 'KeyPath<Array<String>, Int>' to expected argument type '(Array<String>) -> Int'