Day 7 - 100 Days of Swift

5 minute read

Closures (part 2)

Day 7 is the second day about closures. You look at using closures that accept parameters and return values as parameters themselves. You look at some of the things you can do to make closures more condensed. You look at closures that accept multiple parameters, returning closures from functions, and capturing values that are defined outside of the closure.

You can define a parameter to a function as a closure that accepts a parameter like this (I inadvertently did this yesterday in one of the last code snippets):

1
2
3
func greet(_ name: String, with greeting: (String) -> Void) {
    greeting(name)
}

In the same vein, you can define a parameter as a closure that returns a value like this:

1
2
3
4
func greet(with greeting: () -> String) {
    let phrase = greeting()
    print(phrase)
}

Because Swift is so strongly typed, the compiler can infer quite a bit, which means there are lots of things you can do to make your closures more terse if you want to (you don’t have to, and it isn’t always the most readable, but it is possible):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
let studyGroup = [
    "Annie",
    "Jeff",
    "Britta",
    "Troy",
    "Shirley",
    "Abed",
    "Pierce"
]

let beginningOfTheAlphabet1 = studyGroup.filter() { (name: String) in
    return name < "G"
}

// Can drop the type of 'name' because it can be inferred
let beginningOfTheAlphabet2 = studyGroup.filter() { name in
    return name < "G"
}

// Can drop return, because our code is only one line
// and the filter closure has to return a Bool
let beginningOfTheAlphabet3 = studyGroup.filter() { name in
    name < "G"
}

// Can also drop the name and use the shorthand name
let beginningOfTheAlphabet4 = studyGroup.filter() {
    $0 < "G"
}

// Can also drop the parenthesis and it all fits on one line
let beginningOfTheAlphabet5 = studyGroup.filter { $0 < "G" }

// All five of these variables will be equal to:
// ["Annie", "Britta", "Abed"]

Just like a function, closures can accept multiple parameters:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
func transformNumbers(_ numbers: [Int],
                      by modifier: Int,
                      with transformation: (Int, Int) -> Int) -> [Int] {
    var results: [Int] = []
    for number in numbers {
        let result = transformation(number, modifier)
        results.append(result)
    }
    return results
}

let exponentiate = { (number: Int, exponent: Int) -> Int in
    var result = 1
    for _ in 0..<exponent {
        result *= number
    }
    return result
}

let cubes = transformNumbers([2, 3, 4], by: 3, with: exponentiate)
print(cubes)
// Prints: [8, 27, 64]

You can also return a closure from a function:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
func generateNamePicker() -> () -> String {
    return {
        var names = [
            "Annie",
            "Jeff",
            "Britta",
            "Troy",
            "Shirley",
            "Abed",
            "Pierce"
        ]
        names.shuffle()
        return names[0]
    }
}

let namePicker = generateNamePicker()
print(namePicker()) // Prints one name from the list
print(namePicker()) // Prints one name from the list
print(namePicker()) // Prints one name from the list

If the closure accesses value outside of itself, those values are captured and kept alive for use within that closure:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// The previous function can be improved:
func generateNamePicker(with names: [String]) -> () -> String {
    var names = names
    names.shuffle()
    return {
        if names.count > 0 {
            return names.popLast()!
        } else {
            return "No names left!"
        }
    }
}

let namePicker2 = generateNamePicker(with: studyGroup)
for i in 0..<8 {
    print(namePicker2())
}
// Each name will be picked and printed once
// The eighth (and all subsequent times) through the loop
// will result in "No names left!"

Reflections

I am pretty comfortable with giving closures to a function to run, but returning closures from a function is something that I don’t have much experience with. That pattern, combined with the ability to capture variables when you build the closure, seems like it could be pretty powerful when it is the tool you need. I haven’t really been able to thing of any practical examples yet, beyond just being able to demonstrate that it is possible. Now that I have the idea in my head though, I’m sure I’ll come across a reason to use it somewhere.