Enough About Swift Closures to Choke a Horse
The following examples represent variations on a theme. Each of these closures compares two Ints (integers) and returns a Boolean truth-value, but each does so in slightly different ways. Combined, they give you a sense of the expressive flexibility of the Swift language.
For example, here's a really simple closure that implements its functionality using implicit parameter naming. With implicit naming, parameters are defined by position, starting with $0 for the first parameter and then continuing to $1, $2, and so on.
var testEquality1 : (Int, Int) -> Bool = { return $0 == $1 }
This first variation omits the "return" keyword because it can be implied from the context. When working with a closure with a return type, Swift assumes that a single line creates a return value. What you get is an even simpler closure than the first example.
var testEquality2 : (Int, Int) -> Bool = { $0 == $1 }
This next variation adds a parameter declaration within its closure. This declaration allows each parameter to be referred to by name instead of position as in the earlier examples. It uses the rather odd syntax of parameters followed by a return type (with the -> syntax) and then the "in" keyword. It makes more sense when you think of this as "Use these parameters in these statements."
var testEquality3 : (Int, Int) -> Bool = { (a : Int, b : Int) -> Bool in return a == b // or a == b for the implied return }
The following parameters use a postfix form of Type followed by the variable name. It's a slight variant of the normal variable name, colon, type sequence but compiles and executes correctly.
var testEquality3Alternative : (Int, Int) -> Bool = { (Int a, Int b) -> Bool in return a == b // or a == b for the implied return }
Inferred Types
Inferred types further simplify that parameter declaration. The following variation uses (a, b) instead of (Int a, Int b) because each type is known from its position. This enables you to skip the two Int keywords within the closure when you declare your parameter names.
var testEquality4 : (Int, Int) -> Bool = { (a, b) -> Bool in return a == b // I'll stop mentioning the implied return now }
As with the parameter types, the return type isn't really needed in the declaration. This version skips the -> Bool part of the declaration, allowing Swift to infer it on its behalf.
var testEquality5 : (Int, Int) -> Bool = { (a, b) in return a == b }
A fully specified parameter and return item within the closure enables Swift to infer the closure type on your behalf. Therefore, there is no colon before the braces in this next example. This is essentially the opposite behavior to testEquality5.
var testEquality6 = { (a : Int, b : Int) -> Bool in return a == b }
Type Aliases
Declaring a typealias simplifies repeated declarations, particularly when using closures as parameters. This CompareClosureType is equivalent to the (Int, Int) -> Bool used in the previous examples.
typealias CompareClosureType = (Int, Int) -> Bool
You might use this alias in a function declaration to specify a closure parameter, as here. You could pass any of the testEquality variables declared in this write-up to this function.
func testClosure(fn : CompareClosureType) -> String {...}
Type aliases make your code much more readable. For example, this closure mirrors testEquality1 buts use an alias for the type after the colon. The result is simpler and cleaner than the original example.
var testEquality1b : CompareClosureType = { return $0 == $1 }
Using a type alias won't affect any material you declare between the braces, as in this adaptation of testEquality3. Only the initial type declaration is changed.
var testEquality3b : CompareClosureType = { (Int a, Int b) -> Bool in return a == b }
Function Forms
Closures are essentially unnamed functions, so you can easily convert any closure into an equivalent func form. The following example performs the same comparison but adds its type and parameter declarations outside the braces, directly onto the function name.
func testEquality7(a : Int, b : Int) -> Bool { return a == b }
This next func form works on any equatable type – that is, things that can be compared to each other – not just integers. So you can call it with, for example, testEquality8("Hello", "Hello") or testEquality8("Hello", "Bye").
func testEquality8<T : Equatable>(a : T, b : T) -> Bool { return a == b }
The Ultimate Closure
Finally, for the ultra parsimonious there is the following, without a byte wasted.
let testEquality9 : (Int, Int) -> Bool = (==)
Testing Closures
As mentioned earlier, you can pass closures (and functions) as parameters as easily as you would more standard types. The following code enables you to test many of the examples you encountered in this write-up to confirm that they execute properly and return "false, true, false" as expected.
func testClosure(fn : CompareClosureType) -> String { var result = "" result += fn(1,3) ? "true, " : "false, " // should be false result += fn(3,3) ? "true, " : "false, " // should be true result += fn(3,1) ? "true" : "false" // should be false return result } for function in [testEquality1, testEquality2, testEquality3, testEquality4, testEquality5, testEquality1b, testEquality2b, testEquality3b, testEquality6, testEquality7, testEquality8, testEquality9] { println(testClosure(function)) }
Thanks to Phil Holland, especially for the cooler examples.