Functions - RaduG/swift_learning GitHub Wiki

Basics

func hello(who: String) -> String {
    return "Hello, \(who)"
}

Is invoked as:

let greeting = hello(who: "Radu")

By default the argument labels are mandatory. The labels can be different to the actual variable name:

func hello(who name: String) -> String {
    return "Hello, \(name)"
}

let greeting = hello(who: "Radu")

Or can be removed altogether:

func hello(_ name: String) -> String {
    return "Hello, \(name)"
}

let greeting = hello("Radu")

Functions are referred to by their labels:

// someFunc(_:_:_:)
func someFunc(_ a: Int, _ b: Int, _ c: String) {}

// someOtherFunc(a:b:c:)
func someOtherFunc(a: Int, b: Int, c: String) {}

// lastFunc(aa:bb:cc:)
func lastFunc(aa a: Int, bb b: Int, cc c: String) {}

Functions without return statements still return Void.

func someFunc() {
    print("abc")
}

let a = someFunc()
a == Void() // true

Tuples as return values

func minMax(_ arr: [Int]) -> (min: Int, max: Int) {
    let min = arr.min()! // .min returns Int?
    let max = arr.max()! // .max returns Int?
    
    return (min, max)
}

let ret = minMax([10, 20, 30, 1, 2, 3])

// Can access items using indices
print("Min: \(ret.0), Max: \(ret.1)") // Min: 1, Max: 30
// Or labels
print("Min: \(ret.min), Max: \(ret.max)") // Min: 1, Max: 30

Implicit return

If the body of a function is a single expression, its value is implicitly returned.

func sayHello(to name: String) -> String {
    "Hello, \(name)"
}

print(sayHello(to: "Radu"))

Default argument values

Arguments can have default values which means they can be omitted when the function is called. They shouldn't be followed by arguments without default values as a best practice, even though they technically can.

func sayHelloWithLocation(to name: String, from location: String? = nil) -> String {
    if let loc = location {
         return "Hello, \(name) from \(loc)!"
    }
    return "Hello, \(name)!"
}
print(sayHelloWithLocation(to: "Radu"))
print(sayHelloWithLocation(to: "Radu", from: "Romania"))

Variadic arguments

Similar to *args in Python. A function can have at most one variadic argument.

func sum(_ numbers: Double...) -> Double {
    var total: Double = 0
    for n in numbers {
        total += n
    }
    return total
}

sum(10, 20, 30)
sum(15.5)
sum()

Mutable arguments

By default a function's arguments are immutable (constants). To allow them to be mutated, changing the value for the caller as well, use inout before the argument type. Inout arguments are also flagged by the caller with &.

func swapNumbers(_ a: inout Double, _ b: inout Double) {
    let temp = a
    a = b
    b = temp
}

var a = 15.5
var b = 13.7
print("a=\(a), b=\(b)")

swapNumbers(&a, &b)
print("a=\(a), b=\(b)")

Functions are first-class citizens

func sum(a: Int, b: Int) -> Int {
    return a + b
}

let mathFunction: (Int, Int) -> Int = sum

sum(10, 20)

Simple implementation of reduce:

func reduce<T>(_ values: T..., f: (T, T) -> T) -> T? {
    guard values.count > 0 else {
        return nil
    }
    var cumul = values[0]
    for value in values[1...] {
        cumul = f(cumul, value)
    }
    return cumul
}

reduce(10, 20, 30, f: sum)! // 60

Nested functions

func chooseStepFunction(forward: Bool = true) -> (Int, Int) -> Int {
    func moveForward(position: Int, by steps: Int) -> Int { position + steps }
    func moveBackward(position: Int, by steps: Int) -> Int { position - steps }

    return forward ? moveForward : moveBackward
}

var position = 5
position = chooseStepFunction()(position, 2)
print(position)

position = chooseStepFunction(forward: false)(position, 15)
print(position)

Scoping is as expected:

func chooseStepFunction(forward: Bool = true, by steps: Int) -> (Int) -> Int {
    func moveForward(position: Int) -> Int { position + steps }
    func moveBackward(position: Int) -> Int { position - steps }

    return forward ? moveForward : moveBackward
}

var position = 5
position = chooseStepFunction(by: 2)(position)
print(position)

position = chooseStepFunction(forward: false, by: 15)(position)
print(position)

Functions can be bound to the enclosing scope:

func makeIncrementer(forIncrement amount: Int) -> () -> Int {
    var runningTotal = 0
    func incrementer() -> Int {
        runningTotal += amount
        return runningTotal
    }
    return incrementer
}