LEARN SWIFT

Buy the PDF

11.1 Functions

11.1.1 Defining functions

Functions are declared using the func keyword. When declaring a function you provide a type signature for the function along with its implementation.

Functions can be declared on their own or within a class, in which case they become a method that operates on objects of that class. We’ll cover methods in chapter 12.

Here’s an example of a simple function that adds 2 integers.

func intAdder(x: Int, y: Int) -> Int{
    return(x + y)
}
intAdder(3, 4) // 7

We declare a type signature for the function by indicating the type of each of its parameters (x and y) in this example and also its return type ( -> Int indicates that this function returns an integer). The return statement indicates the value that we want our function to return. The above function must include a return statement, since the type signature specified that it returns an integer. If we wanted a function that doesn’t return anything we can specify the return type as Void (or leave out the return type, in which case Swift will infer that the return type is Void).

Here’s an example that doesn’t return anything but running it has the side-effect of printing the value of adding the 2 arguments.

func sideEffectAdder(x:Int, y:Int) -> Void {
    println("Added value is \(x + y)")
}
sideEffectAdder(3, 4) // prints "Added value is 7"

or alternatively you can omit the return type and return statement and swift assumes that the return type is Void. i.e.

func sideEffectAdder(x:Int, y:Int) {
    println("Added value is \(x + y)")
}
sideEffectAdder(3, 4) // prints "Added value is 7"

11.1.2 Local and external parameters

Swift allows us to define both local and external parameter names. So far our examples have used local parameter names. Lets revisit our intAdder function.

func intAdder(x: Int, y: Int) -> Int{
    return(x + y)
}
intAdder(3, 4)

x and y here are local parameter names. They are only used within the function body. When we call the function we don’t refer to the parameter name - we just line up the arguments positionally: intAdder(3,4).

If we have a function with a lot of parameters or we want to improve readability on our function calls we can use external parameter names. We could redefine this function to use external parameter names like this:

func intAdder2(firstNum x: Int, secondNum y: Int)-> Int{
    return(x + y)
}
intAdder2(firstNum: 3, secondNum: 4)

You declare the external parameter name by adding it before the local parameter name as shown in the example above. In this example the first parameter has an internal name of x and an external name of firstNum. Thus within the function body we refer to it as x, but when calling the function we refer to it as firstName.

Once you’ve declared the external parameter names, then you must use them when you call the function. I.e. we can’t call intAdder2(3, 4) - since we have external parameter names we have to use those to name the arguments, so we have to call it as intAdder2(firstNum: 3, secondNum: 4). Note also that we still need to order the arguments correctly when using external parameter names. Calling intAdder2(secondNum: 4, firstNum: 3) will result in a compiler error.

In cases where the internal and external parameter names are the same you can use the shorthand notation of putting a # in front of the name. So we could rewrite our intAdder function as:

func intAdder3(# x: Int, # y: Int) -> Int {
    return(x + y)
}
intAdder3(x: 3, y: 4)

In this example the function intAdder3 has both internal and external parameter names of x and y and since we have declared external parameter names, we must use them when calling the function.

11.1.3 Variadic parameters

Functions can take a variable number of arguments. You do this by adding ... after the final parameter name in your parameter list. All extra arguments will be available in an array with that parameter name.

func sayHello(names:String...)-> String{
    let names = " and ".join(names)
    return("Hello to \(names)")
}
sayHello("Jack") // Hello to Jack
sayHello("Jack", "Jill") // Hello to Jack and Jill

This example defines a function called sayHello which takes a variable number of string arguments in a parameter called names. Since we’ve marked that parameter with ... it will be an array, so we can process it using something that operates on an array, such as a for loop or in this case join. The join function here is called on a string and takes an array as an argument. It then returns a string made of joining all the elements of the array with the joining string.

11.1.4 Returning optionals

A function can return an optional by adding a ? to the end of the return type.

func evenAdder(x:Int, y:Int) -> Int? {
    let even = (x % 2 == 0, y % 2 == 0)
    switch even{
    case (true, true):
        return (x + y)
    default:
        return nil
    }
}
evenAdder(2, 4) // {Some 6}
evenAdder(2, 3) // nil

This function returns an optional. It checks that both of its arguments are even and only if both are even, it returns their sum. Otherwise it returns nil.

11.1.5 Returning multiple values

The idiomatic way to return multiple values from a function is by using a tuple. We saw an example of using a tuple to return multiple values from a function in chapter 8. Here’s that example again.

1 func getNewMail () -> (code: Int, message: String) {
2     return (200, "OK")
3 }
4 
5 getNewMail()

The function in this example returns multiple values using a tuple (line 1). Tuples function as types in Swift, so the return type for a function can be a tuple. The function must then return a tuple which has the same type-signature as the tuple type used in the function declaration.

We can also use typealias to name tuple types. In this next example we create a type called Response which is an alias for a tuple of the form (code: Int, message: String). We can then use Response as a return type for our function.

typealias Response = (code: Int, message: String)

var result:Response
result = (500, "Internal Server Error")

func getNewMail2 () -> Response {
    return (200, "OK")
}

11.1.6 Functions as parameters to functions

Functions in Swift are first-class objects. This means that we can assign functions to variables, pass them as arguments to functions and return them from other functions. The fact that functions are first-class objects in the language opens up a variety of functional programming techniques and allows us to program in a functional style in Swift.

Functions can operate as types. You an use the type signature of a function in any place that Swift expects a type. So you can use functions as arguments to functions and you can return functions from functions.

Here’s a function that takes another function as a parameter.

func capitalize(s:String) -> String {
    return s.capitalizedString
}
capitalize("hello") // "Hello"

func applyToString(s:String, f:(String)->String) -> String {
    return(f(s))
}
applyToString("hello", capitalize) // "Hello"

In this example we our function applyToString takes 2 arguments. The first is a string s and the second is a function f. The function f must be a function that takes a single string argument and returns a string. applyToString then applies that function f to the string s. For example we can define a function capitalize that takes a string as a parameter and returns a capitalized string. This function can then be passed to our applyToString function as a parameter.

This example is a bit contrived and our applyToString doesn’t appear to be any better than just calling our capitalize function directly. But it is a bit more general - it can take any function. When we combine this with the ability to define closures in place (covered in the next section) it becomes much more powerful.

More useful examples where we can pass functions as arguments include builtin functions such as map, reduce and sort. Each of these take a function or closure as an argument which we can use to customize their behaviour. For example, map takes a function that takes 1 parameter and applies it to every element in an array - thus we could apply our capitalize function to every string in an array using map.

["a", "b", "c"].map(capitalize) // ["A", "B", "C"]

11.1.7 Returning functions from functions

You can define a nested function within a function. This limits the scope of the nested function to the parent function. We can also return a function from a function. This (somewhat contrived) example demonstrates both:

func makeLogger() -> (String) -> String {
    func log(s:String) -> String {
        println(s)
        return(s)
    }
    return(log)
}

let myCustomLog = makeLogger()
myCustomLog("I am a message") // I am a message

Here we have a log function that is nested within our makeLogger function. The makeLogger function takes no arguments and returns a function that takes a string as a parameter and returns a string. So makeLogger just returns the nested function log. By returning a function, we can assign the results of makeLogger to a variable or constant and use that name to call the function. Here we assign the returned function to myCustomLog and then we can call that function with myCustomLog("I am a message").

11.2 Closures

Closures are anonymous functions (often referred to as lambdas or blocks in other languages). They are blocks of code that you can pass as arguments to functions where the receiving function expects a function type.

Closures have 2 common use-cases.

1) As a convenient way to define and pass a small function without having to go through the hassle of naming and defining a fully fledged function. When defining a closure, Swift can infer the parameter types and return type from the context in which the closure is defined. Closures also have implicit returns and shorthand for accessing arguments passed to the closure. Thus the syntax for defining a closure is much less verbose than when defining a fully fledged function.

2) As a way of enclosing (closing over) some state from when the closure was defined. You can use a closure to capture and store the state of some variable at a particular point in time (the point when the closure is defined) and store those values to be used later.

There are two ways to define a closure:

  1. closure expressions provide a shorthand syntax for defining inline functions at the place where you are using them.
  2. trailing closures. If you are passing a closure as the final argument of another function and the closure is long, you can optionally define it as a trailing closure.

11.2.1 Closure expressions

Closures are defined using curly brackets and the in keyword. The syntax for a closure is

{ (params) -> return_type in statements }

Lets take a look at an example. One common idiom in functional programming is to transform a sequence by applying a closure to each element using map. Swift’s Array type has such a map function which takes a closure and applies it to each element in the sequence.

We’ll start by just using a standard functions (remember closures are anonymous functions, often used for convenience where there’s no need to define a fully fledged function.).

Let’s say we have an array of numbers and we want to double each number in the array. We can use map and pass it a function that will double the number that is passed as an argument.

let nums = [1, 3, 5, 7]

func doubler(x:Int) -> Int{
    return(x * 2)
    }

let fnDoubledNums = nums.map(doubler) // [2, 6, 10, 14]

Here we have an array of integers. We’ve defined a function called doubler that takes an integer as an argument and returns another integer that is the result of multiplying its argument by 2. We then pass that function as an argument to map, which applies that function to each element in the array.

However, it’s hardly worthwhile defining a function specifically for the purpose of passing it to the map function. We can use a closure instead. Using a closure we’ll define an anonymous function just at the point where we want to use it.

Using the full closure syntax, we can rewrite our example above using a closure and get the same results.

let nums = [1, 3, 5, 7]
let closureDoubledNums = nums.map({ (num:Int) -> Int in return(num * 2) })
closureDoubledNums // [2, 6, 10, 14]

Here we’ve declared a closure that has a single integer parameter called num and returns an integer that is double the value of num.

Because this is such a short closure (i.e. a 1-liner) we can take advantage of the various shorthand notations that closures in Swift support. Because it’s a 1-liner, we can drop the explicit return. Swift assumes we are returning the result of evaluating that single line. We can also remove the type signature as Swift is able to induce the type signature for the closure from the context in which it is used. (i.e. it’s called on a list of integers so it takes an integer parameter, and it returns that parameter multiplied by 2, so its return type is also an integer).

So we can rewrite our closure as

let nums = [1, 3, 5, 7]
let closureDoubledNums2 = nums.map({ num in num * 2 })
closureDoubledNums2 // [2, 6, 10, 14]

Here we just provide a name (num) for the argument that gets passed to the closure.

We can shorten this further if we want (depends on how readable you find each form). Swift allows us to refer to arguments passed to closure using $0, $1, $2 etc. So in this case, our closure gets passed a single parameter and that parameter can be accessed using $0.

So we can rewrite our closure again as:

let nums = [1, 3, 5, 7]
let closureDoubledNums3 = nums.map({ $0 * 2 })
closureDoubledNums3 // [2, 6, 10, 14]

So in summary, the code { $0 * 2 } defines an anonymous function or closure that takes a single argument and returns that argument multiplied by 2. This function is defined in place and passed to the map method that is part of the Array class, which applies that function to each element of the array. I.e. for each element of the array it calls that closure with the element as its single argument.

11.2.2 Trailing closures

When the closure is being passed as the final argument to a function we can pass it as a trailing closure. You do this by putting the closure definition directly after the function call instead of as a function paramter. This is generally reserved for when the closure is long and it may be more readable to move the definition of the closure outside of the function argument list. Here’s how we would rewrite our previous example to use a trailing closure, instead of passing the closure as an argument to map.

let nums = [1, 3, 5, 7]
let trailingClosureDoubledNums = nums.map() {
    (num:Int) -> Int in
    return(num * 2)
}
trailingClosureDoubledNums // [2, 6, 10, 14]

Similar to our previous example we can shorten the closure here but still leave it as a trailing closure.

let trailingClosureDoubledNums2 = nums.map() { $0 * 2 }
trailingClosureDoubledNums2 // [2, 6, 10, 14]

11.2.3 Using a closure to close over a value

Another use of a closure is to “close over” the value of something at a particular point in time. A closure can store some state when it is created and save it until it is called at some future point in time. More specifically, a closure can save the value of any parameter that is in scope when it was declared, and can continue to use that value even after the original parameter is no longer in scope and has disappeared.

Lets continue with our example where we want to double each item in an array.

Lets say we have a more general function that multiplies a number by a multiplier. What we’d like to do here is create a function that takes a multiplier and a number and returns a function that multiplies the number by the multiplier. We’ll create this as a closure so that we can close over the value of the multiplier and continue to use that value even after the original scope of that parameter has disappeared.

Here’s a function that will return such a multiplier function.

 1 let nums = [1, 3, 5, 7]
 2 
 3 func makeMultiplier(multiplier:Int) -> (Int) -> Int {
 4     return {
 5         (num:Int) -> Int in
 6             return(num * multiplier)
 7     }
 8 }
 9 
10 let doubler2 = makeMultiplier(2) // (Function)
11 doubler2(10)       // 10
12 let tripler = makeMultiplier(3) // (Function)
13 tripler(10)        // 30
14 nums.map(doubler2) // [2, 6, 10, 14]
15 nums.map(tripler)  // [3, 9, 15, 21]

First let’s look at the type signature for the function makeMultiplier above. The function takes a single integer argument called multiplier. It returns a function. Note that the return type-signature for the makeMultiplier function is actually a type signature for another function ((Int) -> Int). So it returns a function that takes a single integer argument and returns an integer.

The function that we are returning is created as a closure (line 4). That closure is a function that takes a single argument, num, and returns num multiplied by the value of the multiplier parameter that was passed to makeMultiplier. This closure “closes over” the value of multiplier so that we can continue to use it when it has gone out of scope.

Consider an example of how we would use this function. Say we wanted to create a function that takes a single integer parameter and doubles it. We can do that as shown in line 10 above: var doubler2 = makeMultiplier(2). This returns a function that takes a single argument and multiplies it by 2. In this case the multiplier parameter to makeMultiplier had value 2 when we created the function, and the closure that we create closes over that value so that that closure will always see multiplier as having value 2.

We can also use makeMultiplier to make another function that triples whatever value is passed to it (line 12 above).

In our above example we use makeMultiplier to create our closure and we save it into a variable using let. This lets us name our closure so that we can call it later. E.g. at line 12 we create a closure that triples the argument that is passed to it and save that function as tripler. We can then use this name to call it directly as a function (line 13) or to apply it to every element in our array using map (line 15).

Lets take another example that uses a closure. Lets make a logging function that logs a message and a log level. We can do this using closures in a similar way to how we made a multiplier function in our previous example.

 1 func makeLevelledLogger(level:String) -> (String) -> String {
 2     return {
 3         (message:String) -> String in
 4         let logMessage = "\(level) :: \(message)"
 5         println(logMessage)
 6         return(logMessage)
 7     }
 8 }
 9 let debug = makeLevelledLogger("DEBUG")
10 let warning = makeLevelledLogger("WARNING")
11 let message = makeLevelledLogger("MESSAGE")
12 
13 debug("A message from our debug logger") // "DEBUG :: A message from our debug
14                                          // logger"
15 warning("Consider yourself warned")     // "WARNING :: Consider yourself warned"
16 
17 message("Life, the universe, and everything...") // "MESSAGE :: Life, the
18                                         //universe, and everything is..."

Our makeLevelledLogger function here takes a single string argument, which is our debug level that we will use for logging, and returns a function. The function that is returned closes over the value of level and takes a single string argument message which it prints along with the log level.

We can use makeLevelledLogger to create a function for each of the log levels we want in our program. We create 3 different functions for different log levels at lines 9-11 and save them as constants. Now we can call those functions with a message when we want to log a message at each level (lines 13-18).