Search code examples
iosswiftcompilationtype-conversion

Swift - Missing function argument does not fail during compilation and even result in a different data types?


I was just experimenting with some Swift code samples and by making a mistake I've found our very weird behaviour (to me) that I don't understand.

I've defined this function:

func makeIncrementer(number: Int) -> ((Int) -> Int) {
  func incrementer(inputNumber: Int) -> Int {
    return inputNumber+1
  }
  return incrementer
}

*yes, the parameter number in not used, that was unintentional mistake, but lead to finding out this weird behaviour I'm asking now.

Then I called the code:

let functionVariable = makeIncrementer(number: 7)
print(functionVariable)
print(functionVariable(7))

which resulted in this output:

(Function) 
8

after this I realized that the number parameter in makeIncrementer function does not make sense, so I've deleted the parameter in makeIncrementer invocation, but xCode didn't show any error message. So I just tried to rebuild this:

let functionVariable = makeIncrementer(number: )
print(functionVariable)
print(functionVariable(7))

And I've received this output:

(Function)
(Function)

Here are my questions:

  1. How is it possible it can be built? Parameter number is not optional, or is it?
  2. Even if the parameter number would be optional, how can it result in two objects that are type of function?

Thank you very much for a explanation

*[EDIT] - corrected typo, originally there was "print(functionVariabl" instead of print(functionVariable)


Solution

  • In your second example you aren't calling the function, but assigning it to the variable using its overload identifier. This is also why it is printing (Function) twice; the first is the original function, the second is the returned curried function.

    Here is an example defining an overload for your function that accepts a String showing that let functionVariable1 = makeIncrementer(number:) is assigning the specific overload of your function that accepts an Int to the variable. Calling this variable functionVariable1(4) now no longer needs (and won't accept) a parameter label, and returns your curried function. Thus, calling print(functionVariable1(4)(11)) will now print the input + 1 as expected.

    func makeIncrementer(number: Int) -> ((Int) -> Int) {
        func incrementer(inputNumber: Int) -> Int {
            return inputNumber + 1
        }
        return incrementer
    }
    
    func makeIncrementer(string: String) -> ((Int) -> Int) {
        func incrementer(inputNumber: Int) -> Int {
            return inputNumber + 1
        }
        return incrementer
    }
    
    let functionVariable1 = makeIncrementer(number:)
    print(functionVariable1)
    print(functionVariable1(4)(11))
    // no error because the overload expecting an Int is assigned to functionVariable1
    
    let functionVariable2 = makeIncrementer(string:)
    print(functionVariable2)
    print(functionVariable2("string")(11))
    // no error because the overload expecting a String is assigned to functionVariable2
    

    For function definitions that accept more than one parameter you can list parameter labels sequentially to assign to a variable, this also removes the need for labels in later calls.

    func fn(number: Int, string: String) {
        print("number: \(number)")
        print("string: \(string)")
    }
    
    let assigneOverload = fn(number: string:)
    assigneOverload(1, "swift")