Understanding Function Types

I am having trouble understanding function types as discussed in Documentation.

I have three aliases, and they are all equal to one another, the runtime types of function and otherFunction are also equal, that tells me all of these can be used interchangeably. Yet, the following commented lines error out.

What underlying concept have I not understood properly?

typealias aliasOne = ((Void)) -> Void
typealias aliasTwo = () -> ()
typealias aliasThree =  (()) -> ()

func function(_ :()) -> Void {}
func otherFunction() -> Void {}

// all are true
print(aliasOne.self == aliasTwo.self)
print(aliasTwo.self == aliasThree.self)
print(aliasOne.self == aliasThree.self)
print(type(of: function) == type(of: otherFunction))

let f1: aliasOne = function
// let f2: aliasTwo = function
let f3: aliasThree = function


// let otherF1: aliasOne = otherFunction
let otherF2: aliasTwo = otherFunction
// let otherF3: aliasThree = otherFunction

PS: I opened an issue in Need More Clarity in "Function Type" Reference · Issue #324 · apple/swift-book · GitHub but I realized I need more clarity here before I attack docs :upside_down_face: .

One is a function with zero arguments, the other two functions with one argument of Void i.e. empty tuple () type.

I think you got confused by the function arrow syntax which really always has a single pair of parentheses around the parameter list, so there's always this bit of fixed syntax (…) -> … in the function type no matter what the are.

Substituting each type () with the typealias Void in the code below:

we get:

typealias aliasOne = ((Void)) -> Void
typealias aliasTwo = () -> Void
typealias aliasThree =  (Void) -> Void

The extra parentheses in aliasOne are redundant, so that's why it's the same type as aliasThree.

4 Likes

Thanks for responding.

If I do typealias aliasOne = (Void) -> Void I get the following warning.

warning: when calling this function in Swift 4 or later, you must pass a '()' tuple; did you mean for the input type to be '()'?

Is it possible for you to tell me why all the three aliases are equal when used with ==? If they are equal then shouldn't my commented code (in my original post) work?

I (think) I understand "this takes no args" vs "this takes an arg that is Void", but I still don't understand how the types are same then (as far as == is concerned).

To be a bit more precise

print(type(of: function) == type(of: otherFunction)) this prints true so I would expect this to work.

// none of these work despite the aliases being equal

let f2: aliasTwo = function
let otherF1: aliasOne = otherFunction
let otherF3: aliasThree = otherFunction
1 Like

There used to be a time in Swift when functions were kind of modelled as taking a tuple of arguments.

Some of that history is still present today for convenience, allowing you to pass 2-argument closures like { foo($0, $1) } to functions taking a function argument of matching tuple type ((A, B)) -> R, rather than requiring to use a single tuple parameter like { foo($0.0, $0.1) }. I think you're observing type system level side-effects of that history here.

In practice, there's hardly a good reason to define a function taking a single Void as argument, except generically.

3 Likes