Generic Programming - ADDITIONAL TOPICS - Understanding Swift Programming: Swift 2 (2015)

Understanding Swift Programming: Swift 2 (2015)

PART 3: ADDITIONAL TOPICS

28. Generic Programming

EnEnSwift, like many other modern languages, has capabilities that support generic programming. Generic programming allows a programmer to write code that works for more than one data type.

The Problem

The need for generic programming in Swift actually results from Swift's high degree of type safety. In every piece of code, we are very aware of the data type of variables, constants, and the like. However, forcing variables and constants to be a particular type makes it difficult to write code that works for more than one type. We can see the problem in the examples below.

Suppose we have two integers stored in variables:

var integer1 = 5

var integer2 = 7

And we'd actually like to swap the values, so that integer1 becomes 7 and integer2 becomes 5.

We can write a function to do this:

func swapIntegerValues(inout a: Int, inout b: Int) {

var temporaryValue = a

a = b

b = temporaryValue

}

And execute it with:

swapIntegerValues(&integer1, b: &integer2)

print("integer1=\(integer1) and integer2=\(integer2)")

// Prints: integer1=7 and integer2=5

The only thing a little unusual about this is that instead of passing the values to a function and getting one or more return values, we are passing them as "inout" values. The "&" in front of the variable names in the function call, and the inout keyword in the input parameter definition in the function, indicates that these are inout values, meaning that the global variables integer1 and integer2 are passed as parameters, accessed, and can even be changed, within the function.

Now suppose we want to do the same thing with a pair of floating point numbers:

var floating1 = 5.1

var floating2 = 9.2

Our function for swapping integers won't work—it only works with the type of Int. We'd normally have to write another function:

func swapFloatingValues(inout a: Double, inout b: Double) {

var temporaryValue = a

a = b

b = temporaryValue

}

swapFloatingValues(&floating1, b: &floating2)

print("floating1=\(floating1) and floating2=\(floating2)")

// Prints: floating1=9.2 and floating2=5.1

This gets annoying, even for this simple function, if we want to use it with various of other types—say Float or String. For every type, we need a different function. If we had a complicated function with lots of different input values, each with several different types, it would get to be rather time-consuming to write functions to handle all the possibilities.

The Generic Solution

Generic programming allows us to write a single, generic function that has a generic type, represented with the symbol T:

func swapGenericValues<T> (inout a: T, inout b: T) {

var temporaryValue = a

a = b

b = temporaryValue

}

It is used, and called, in exactly the same way as for the function that uses integer types only:

var integer1 = 5

var integer2 = 7

swapGenericValues(&integer1, b: &integer2)

print("integer1=\(integer1) and integer2=\(integer2)")

// Prints: integer1=7 and integer2=5

But we can now use it with other types as well.

To use generic programming to define a function, we make two changes. First, we add, just after the function name, one or more parameters contained within angle brackets. In this case, there is a single T, known as a placeholder type, that represents a type that is specified only generically. In this case, it can be any type. And then, wherever an actual type name is specified in the code in the function, we use the placeholder type, in this case T, instead. An upper case T is used by convention, especially when only one type is used, but any legitimate identifier name can be used. It is also possible to specify more than one type parameter, and in that case it is more common to use more meaningful names as identifiers. Camel case notation is typically used, with the first letter of the identifier in upper case. The placeholder types within angle brackets are known as type parameters.

The rest of this chapter describes some of the different ways that generic programming can be used in Swift. In the example above the generic type can stand in for all possible types. This is actually unusual. Normally, limits are placed on what types are allowable. These limits are known asconstraints, and they are specified by either indicating the names of protocols that the types must conform to, or the names of classes that the types must be subclasses of.

As we saw above, generic programming can be used with functions. But it can also be used with classes, structures, and enumerations.

Generic Functions and Constraints

We can look at a different function to see an example where we need constraints:

func compareTheseValues<T> (a: T, b: T) {

if a == b {

print("The two values are equal")

}

else {

print("The two values are NOT equal")

}

}

We can call this with the following:

compareTheseValues(5, b:7)

// Prints: error: cannot invoke '==' with an argument list of type '(T, T)

In fact, this function as described will not work. We get an error message, because the double equals sign comparison operator won't work with just any type. We have to constrain the allowable types:

func compareTheseValues<T: Equatable> (a: T, b: T) {

if a == b {

print("The two values are equal")

}

else {

print("The two values are NOT equal")

}

}

compareTheseValues(5, b: 7)

// Prints: The two values are NOT equal

This version of the function works. In this case, we specified the placeholder type T with a colon after it and then the name of a protocol, Equatable. This specifies that the function will only work for types that conform to the Equatable protocol. This is our constraint. We could have alternatively specified the name of a class, which would mean that the function would only work for types that are subclasses of the class that was specified.

The Equatable protocol requires types that conform to it to implement two operators, the == operator and the != operator.

Multiple Placeholder Types and Names

In the examples above we just used a single placeholder type, and called it T:

<T>

<T: Equatable>

The use of T is arbitrary and is just a convention to use when you have a single placeholder type. If you have two, its common to use T and U. If there are more than two, it is common to use meaningful names instead of T and U.

<T,U>

<TreeLength, TreeDiameter, TreeSpecies>

It is customary to use camel case, with the first letter in upper case.

Generic Classes and the Like

In some situations generic functions aren't enough—you want to use a class, structure, or enumeration. You might want to define a variable in a class, say, in generic terms, and you might want the variable to be accessible by an initializer and/or method and refer to it in generic terms in that initializer or method.

Generic types are defined in classes, structures, and enumerations in exactly the same way that they are for functions. Placeholder types for one or more generic types go between angle brackets just after the name of the class, structure, or enumeration. They can be referred to in variables, constants, initializers, or methods. The scope of the placeholder is the entire class, structure, or enumeration. So if you define a class, structure, or enumeration as having a particular placeholder, such as T, you can use the placeholder T within a method of the class, structure, or enumeration without specifically defining it for that method.

The following example shows a structure with a definition that uses a placeholder type:

struct Queue<T> {

var queue = [T]()

mutating func put(item: T) {

queue.append(item)

}

mutating func get() -> T? {

var n = queue.count

if n > 0 {

var itemFromQueue = queue[0]

for var i = 0; i < n-1; i++ {

queue[i] = queue[i+1]

}

queue.removeAtIndex(n-1)

return itemFromQueue

}

else {

return nil // If no item

}

}

}

We can put a series of integers into the queue as follows:

var queue = Queue<Int>()

queue.put(5)

queue.put(8)

queue.put(2)

And get it out as follows:

print(queue.get())

// Prints: Optional(5)

Hands-On Exercises

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

For Chapter 28 exercises, go to

understandingswiftprogramming.com/28