Functions Revisited: First Class Citizens - ADDITIONAL TOPICS - Understanding Swift Programming: Swift 2 (2015)

Understanding Swift Programming: Swift 2 (2015)

PART 3: ADDITIONAL TOPICS

17. Functions Revisited: First Class Citizens

In Swift, functions are "first class citizens", meaning that they can be used in the same ways that values and variables can be: they can be assigned to variables and constants, they can be passed as input parameters to functions (and closures), and they can be passed as return values from functions and closures.

The term comes from a comment made in the 1960s about procedures in Algol being "second class citizens" because they could not be used in these ways, unlike values, variables, and other entities in the language. Most of today's modern languages now have this capability of treating functions as first class citizens. Functions that can be passed in this way and that can use functions as input parameters and return values are often called "higher order functions".

In this chapter I will first describe how functions can be passed in these ways, and then discuss perhaps the most interesting kind of function as first class citizen, the nested function, which allows you to define one function within another and capture the variable context as you pass the inner function to the outside. That function can then be assigned to a variable on the outside and executed, making use of the environment that was created in the nested function.

Assigning a Function to a Variable

To assign a function to a variable, first define it:

func addTwoNumbers (a:Int, second b:Int) -> Int {

var c = a + b

return c

}

Then assign it by name to a variable:

var add = addTwoNumbers

The compiler will infer a type for add, which will be the type of the function—its signature, the input parameter types, return value, and return types. You can also define it explicitly if you want:

var add:(Int,second: Int) -> (Int) = addTwoNumbers

You can now execute it with the variable add:

print(add(2, second:7)) // Prints: 9

This isn't terribly exciting, since it is just doing what we could do anyway with the original name of the function. But we can also do this when we obtain a function by calling another function.

Returning a Function from Another Function

We start by putting our addTwoNumbers function inside of another function:

func giveMeTheAddFunction () -> ((Int, second: Int) -> (Int)) {

func addTwoNumbers (a: Int, second b: Int) -> Int {

var c = a + b

return c

}

return addTwoNumbers

}

We then execute that function and assign the result to a variable.

var d = giveMeTheAddFunction()

We can now add numbers by executing that function:

print(d(3, second: 4)) // Prints: 7

The first line of the giveMeTheAddFunction, which has two return arrows in it, looks rather strange. Does giveMeTheAddFunction have two return values? No. The second return arrow is part of the definition of the type that giveMeTheAddFunction is returning, which is itself a function that has a return arrow contained in its type.

This is very much like we will do in the last part of this chapter—create a function nested within another function, and then pass that function outside of the outer function. The difference is that with this example we only care about the procedure the function executes—adding two numbers together. We don't need the surrounding environment of variables and the like to be captured.

Passing a Function in an Input Parameter

We can also pass a function to another function as an argument. To do this, we create the function iDontKnowHowToAdd.

Like the giveMeTheAddFunction above, the function includes the type of another function in its definition—the type of the addTwoNumbers function. It's very easy to make a mistake when typing in these kinds of types, and the mistakes aren't necessarily that easy to detect. So we can use a little trick: Defining the type of the function that we are passing with a typealias. We do this as follows:

typealias twoIntsInAndOneOut = (Int, second: Int) -> (Int)

Now when we need to use the expression (Int, second: Int) -> (Int) we just use twoIntsInAndOneOut instead:

func iDontKnowHowToAdd(f: twoIntsInAndOneOut, a: Int, b: Int) -> Int {

var c = f(a, b)

return c

}

Using the typealias also makes the code more readable and, frankly, less weird-looking. There are three input parameters, the function addTwoNumbers and two integers. We've given a local name to the function f with that type (defined by the typealias), and we've also given local names of a and b to the integers that are the second and third input parameters.

Now, of course, the idea here is that the iDontKnowHowToAdd function supposedly cannot add (although this is really a fiction), and so we pass in the addTwoNumbers function so that it has the capability to add. We also pass in two integers that we want to be added. The return value isInt, the type of the value of the sum of the integers that the iDontKnowHowToAdd function will end up returning.

In the actual body of the function, we execute the function we passed in by referring to the local name f and use the local names a and b as input parameters. The sum is assigned to c, which is then returned.

To actually run this, we first define the addTwoNumbers function:

func addTwoNumbers (a: Int, second b: Int) -> Int {

var c = a + b

return c

}

We then define the iDontKnowHowToAdd function:

typealias twoIntsInAndOneOut = (Int, second: Int) -> (Int)

func iDontKnowHowToAdd(f:

twoIntsInAndOneOut, a: Int, b: Int) -> Int {

var c = f(a, second: b)

return c

}

We then execute the iDontKnowHowToAdd function with two integers:

var m = iDontKnowHowToAdd(addTwoNumbers, a: 2, b: 7)

print(m) // Prints: 9

The parameter names a and b are required in Swift 2. They should not be used in Swift 1.

Nested Functions and Capturing the Environment

In attempting to understand functions and closures, and particularly the business of capturing surrounding variables and constants, it can be helpful to look at nested functions. Nested functions are when one function is defined inside another function. We actually saw a nested function earlier in the giveMeTheAddFunction example. But this did not demonstrate the full power of a nested function, including that of capturing the environment. The inside, or inner, function cannot be directly called by code outside of the outer function, although that function can be passed as a return value to the code outside and then called once it is outside. We'll see an example of this below.

A global function, which is to say a function that is in the global scope—at the highest level of the code and not inside another function—cannot capture the surrounding environment of variables, constants, and their values. Only a function nested within another function can do this.

The example of a nested function shows how a function, which in Swift can be passed around like it is a variable, literal or type, and can be passed as a return value from another function. In addition, the example shows how variables and their values can be captured and used later, even when they have gone out of scope in their original definition, as part of the "closure" mechanism.

This pair of functions keeps track of the elevation of a person who is traveling in the mountains. It has a property, currentElevation, that is initially set to 0 (sea level) when the outer function, initializeElevation, is initially called. The outer function then returns the inner function, updateElevation. The outer function is called only at the beginning and when you want to start over at an elevation of 0.

The returned inner function, updateElevation, is then assigned to a variable f, which effectively becomes a function and can be executed by referring it using the same syntax as a function, in this case by using its name and a pair of parentheses after the name, with a number within the parentheses to indicate the amount of change (in feet) in elevation, e.g., as f(100). Calling the function f in this way will update the property currentElevation variable and print out its current value. Calling the function with a new value reflects the traveler making a change in elevation by the amount specified; the property currentElevation reflects the overall elevation level that the function keeps track of.

Here's the code:

.

func initializeElevation() -> (Int) -> () {

print("Initialize elevation to 0 feet")

var currentElevation = 0

func updateElevation(updateAmount: Int) {

currentElevation += updateAmount

print("Update \(updateAmount); currentElevation is now \(currentElevation) feet")

}

return updateElevation

}

This is mostly just a normal Swift function inside another Swift function. Again, the outer function has two return arrows, with just part of the type of the function being returned.

We can now call the outer function. Because the outer function is returning the inner function as a return value, we are assigning the inner function to the variable f:

var f = initializeElevation()

We can now execute the inner function, by calling the function f with a number for an elevation change:

f(100) // Prints: Update 100; current elevation is now 100 feet

And again:

f(100) // Prints: Update 100; current elevation is now 200 feet

And:

f(500) // Prints: Update 500; current elevation is now 700 feet

And:

f(-300) // Prints: Update -300; current elevation is now 400 feet

Now, f is not a very good name for this function. If we try to use the name updateElevation, it will not work, because an inner nested function cannot be called from outside (except by doing just what we have done, passing it outside as a return value).

We can, however, assign that return value to a variable with the name updateElevation, and then we will be calling it with a more descriptive name.

var updateElevation = initializeElevation()

// Prints: Initialize elevation to 0 feet

We have initialized the elevation to 0 again.

And now we can use the name updateElevation as a function:

updateElevation(400)

// Prints: Update 400; current elevation is now 400 feet

The call to updateElevation executes the inner function that has been stored in the variable updateElevation. This updates the variable currentElevation, which, once the outer function has finished executing and returned a value, has gone out of scope and been released and likely has been deallocated from memory. How, then, can the call to updateElevation work? Only because the variable and its value have been captured, along with the ability to update that value.

Only the function that has been returned to the global scope can now access the variable currentElevation. It cannot otherwise be accessed from the global scope, and the function that created it has by now likely disappeared.

Hands-On Exercises

Go to the following web address with a Macintosh or Windows PC to do the Hands-On Exercises.

For Chapter 17 exercises, go to

understandingswiftprogramming.com/17