My understanding from the Swift docs is that argument labels, not just argument datatypes, are intrinsic to the function signature at compile time. Thus, the compiler understands that these two versions of f
have different types: they both take one String
argument, but the label is different.
func f(foo str : String) -> String { "foo" }
func f(bar str : String) -> String { "bar" }
f(foo: "hi") // "foo"
f(bar: "hi") // "bar"
I’m perplexed, however, by the behavior below. g
and h
also have different types, because they have different argument labels. But this code compiles and runs, even though the argument label is missing:
func f(foo str : String) -> String { "foo" }
func f(bar str : String) -> String { "bar" }
func g(foo str : String) -> String { f(foo: str) }
func h(bar str : String) -> String { f(bar: str) }
(0 < 100 ? g : h)("hello") // "foo" (why?)
But if we introduce a second h
with a different argument label, the code cannot compile, because now we have two conflicting types of h
:
func f(foo str : String) -> String { "foo" }
func f(bar str : String) -> String { "bar" }
func g(foo str : String) -> String { f(foo: str) }
func h(bar str : String) -> String { f(bar: str) }
func h(foo str : String) -> String { f(foo: str) }
(0 < 100 ? g : h)("hello") // ambiguity error
My question: why, in the second example, does the Swift compiler take a more lenient view of type checking? I understand that there is no type ambiguity. But the behavior still seems to break with Swift’s very strong policy about return types of expressions needing to match.
If we believe that argument labels are part of the type signature, then an expression like 0 < 100 ? g : h
shouldn’t even compile, because g
and h
have different argument labels, and hence different types.
CodePudding user response:
Fact: any function in Swift can be reduced to a closure, and they actually are when passed around. f
and g
and h
can all be reduced to closures of type (String) -> String
.
Next, your (some_computation)("hello")
code what in fact does is to tell to the compiler that you want to create an anonymous function (aka closure), that you call with the "hello" argument.
And due to the above, (0 < 100 ? g : h)("hello")
instructs the Swift compiler to transform the g
and h
functions to closures of type (String) -> String
. And closures don't have argument labels, thus any function, with any label, matches.
However, in the problematic code snippet, the compiler can no longer uniquely identify which h
"overload" to use. Thus, it bails out.
If you want to unambiguously match one of the h
functions, then you need to fully qualify it:
(0 < 100 ? g : h(foo:))("hello")
The rules work the same for member functions, as long as the compiler can decide which closure to pass along, it will match the function name, without having to specify its complete name.
Main point is that in Swift you don't circulate functions, you circulate closures. There's no way to specify a variable, or a function argument, or a property, to have labeled arguments.
CodePudding user response:
My question: why, in the second example, does the Swift compiler take a more lenient view of type checking? I understand that there is no type ambiguity.
That second sentence is the entirety of it. It is convenient (and was more obvious in early Swift when functions were defined by currying). There is no particular reason to forbid it. So it's allowed. Would you prefer that this fail when there is no ambiguity?