Why not just use closures (never using functions) or why not just use functions (never using closures)?
Both functions and closures are a way of isolating some functionality, and which may (or may not) take input and may (or may not) return output. In most cases, you will not have to think about “Am I using a function or a closure here?”, as it will all happen behind the scenes. Both functions and closures can be treated as variables and “passed around”. For instance, let’s define a closure and a function:
import Foundation
func function1(string: String, int: Int) -> Data {
let stringData = try! JSONEncoder().encode(string)
let intData = try! JSONEncoder().encode(int)
return stringData + intData
}
let closure1 = { (string: String, int: Int) -> Data in
let stringData = try! JSONEncoder().encode(string)
let intData = try! JSONEncoder().encode(int)
return stringData + intData
}
Both have the type (Int, String) -> Data
, and so you can pass them to any function that takes that type as input. For instance, if you have an array of (Int, String)
tuples, then map
function takes a closure or function of type (Int, String) -> T
and returns an array of type [T]
(T
can be type). Since our closure and function have the same type, both of the following work:
[("One", 2)].map(function1)
[("One", 2)].map(closure1)
So why do we have both? There are probably many more reasons, but there are two I think of immediately. First, functions preserve argument labels, but closures do not. So to call the closure and function directly:
function1(string: "One", int: 2)
closure1("One", 2)
closure1(string: "One", int: 2) // this results in a compile error
The function call is clearer, as the argument labels can be used. This also allows us to overload a function name, or use the same name with the same type arguments:
func function1(repeating string: String, count: Int) -> Data {
let repeatedString = String(repeating: string, count: count)
let data = try! JSONEncoder().encode(repeatedString)
return data
}
Note that this has the same name (function1
) and same type ((Int, String) -> Data
), but has different functionality. Now that the function names are identical, we can refer to them by adding their parameter labels:
[("One", 2)].map(function1(string:int:)) // [[34, 79, 110, 101, 34, 50]]
[("One", 2)].map(function1(repeating:count:)) // [[34, 79, 110, 101, 79, 110, 101, 34]]
[("One", 2)].map(function1) // this results in a compile error, since it is ambiguous
We can't duplicate this behavior with closures, since we can only ever have one variable called "closure1
".
One the other hand, closures have some features that functions do not, including "capturing" their surrounding context (see the Swift book for examples). In addition, while functions must be defined in your code at compile time, closures can be dynamically constructed while your program is running.
As an example, consider the following function, which creates closures:
func makeStringModifier(double: Bool, reverse: Bool) -> (String) -> String {
let doubler: (String) -> String = { String(repeating: $0, count: 2) } // 1
let reverser: (String) -> String = { String($0.reversed()) } // 2
let combined: (String) -> String = {
var string = $0
if double { string = doubler(string) }
if reverse { string = reverser(string) }
return string
} // 3
return combined
}
Based on the two booleans that you pass in, you get different closures back. They are all type (String) -> String
(they take a String
and return a String
), but their effects are different, based on the conditions you choose at runtime.
- Create a closure that doubles a string
- Create a closure that reverses a string
- Create a closure that combines the two, according to the input booleans
- Return the created closure
Note that step 3 captures the two closures (doubler
and reverser
) which are created outside its scope.
let identity = makeStringModifier(double: false, reverse: false)
let reverse = makeStringModifier(double: false, reverse: true)
let double = makeStringModifier(double: true, reverse: false)
let doubleAndReverse = makeStringModifier(double: true, reverse: true)
identity("closure") // "closure"
reverse("closure") // "erusolc"
double("closure") // "closureclosure"
doubleAndReverse("closure") // "erusolcerusolc"
It's worth reading through these sections fully:
https://docs.swift.org/swift-book/LanguageGuide/Functions.html
https://docs.swift.org/swift-book/LanguageGuide/Closures.html