Functional Programming in Swift (2014!10!01)
Functional Programming in Swift (2014!10!01)
Contents
1 Introduction
Current Status of the Book . . . . . . . . . . . . . . . . . . .
Acknowledgements . . . . . . . . . . . . . . . . . . . . . . .
2 Thinking Functionally
Example: Battleship . .
First-Class Functions . .
Type-Driven Development
Notes . . . . . . . . . .
6
8
8
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
10
10
17
20
20
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
22
23
23
27
29
31
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
33
33
37
39
43
44
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Notes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
47
5 Optionals
Case Study: Dictionaries . . . . . . . . . . . . . . . . . . . .
Combining Optional Values . . . . . . . . . . . . . . . . . . .
Why Optionals? . . . . . . . . . . . . . . . . . . . . . . . . .
48
48
52
57
6 QuickCheck
Building QuickCheck .
Making Values Smaller
Arbitrary Arrays . . . .
Using QuickCheck . . .
Next Steps . . . . . .
.
.
.
.
.
61
63
67
70
74
74
76
76
77
82
8 Enumerations
Introducing Enumerations
Associated Values . . . .
Adding Generics . . . . .
Optionals Revisited . . . .
The Algebra of Data Types .
Why Use Enumerations? .
85
85
88
91
93
94
96
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
117
121
122
124
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
127
127
134
137
143
12 Parser Combinators
The Core . . . . . . . . .
Choice . . . . . . . . . .
Sequence . . . . . . . . .
Convenience Combinators
A Simple Calculator . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
147
147
150
151
159
163
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
168
169
169
180
186
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
191
191
194
197
200
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
15 Conclusion
202
Further Reading . . . . . . . . . . . . . . . . . . . . . . . . . 202
What is Functional Programming? . . . . . . . . . . . . . . . . 204
Closure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 205
Additional Code
Standard Library . . . . . . . .
Chapter 10, Diagrams . . . . . .
Chapter 11, Generators . . . . .
Chapter 12, Parser Combinators
References
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
206
206
208
213
213
218
Chapter 1
Introduction
youve only just started to learn to program, this may not be the right book
for you.
In this book, we want to demystify functional programming and dispel
some of the prejudices people may have against it. You dont need to have
a PhD in mathematics to use these ideas to improve your code! Functional
programming is not the only way to program in Swift. Instead, we believe
that learning about functional programming adds an important new tool
to your toolbox that will make you a better developer in any language.
Acknowledgements
Wed like to thank the numerous people who helped shape this book. We
wanted to explicitly mention some of them:
Natalye Childress is our copy-editor. She has provided invaluable feedback, not only making sure the language is correct and consistent, but also
making sure things are understandable.
Sarah Lincoln has designed the cover and given us feedback on the design and layout of the book.
Wouter would like to thank Utrecht University for letting him take time
to work on this book.
We would like to thank the beta readers for their feedback during the
writing of this book (listed in alphabetical order):
Chapter 2
Thinking Functionally
Example: Battleship
Well introduce first-class functions using a small example: a non-trivial
function that you might need to implement if you were writing a Battleshiplike game. The problem well look at boils down to determining whether or
not a given point is in range, without being too close to friendly ships or to
us.
As a first approximation, you might write a very simple function that
checks whether or not a point is in range. For the sake of simplicity, we
will assume that our ship is located at the origin. We can visualize the
region we want to describe in Figure 2.1:
The first function we write, inRange1, checks that a point is in the grey
range
area in Figure 2.1. Using some basic geometry, we can write this function
as follows:
typealias Position = CGPoint
typealias Distance = CGFloat
func inRange1(target: Position, range: Distance) -> Bool {
return sqrt(target.x * target.x + target.y * target.y) <= range
}
Note that we are using Swifts typealias construct, which allows us to introduce a new name for an existing type. From now on, whenever we write
Position, feel free to read CGPoint, a pairing of an x and y coordinate.
Now this works fine, if you assume that we are always located at the
origin. But suppose the ship may be at a location, ownposition, other than
the origin. We can update our visualization in Figure 2.2:
We now add an argument representing the location of the ship to our
inRange function:
func inRange2(target: Position, ownPosition: Position,
range: Distance) -> Bool {
let dx = ownPosition.x - target.x
let dy = ownPosition.y - target.y
let targetDistance = sqrt(dx * dx + dy * dy)
return targetDistance <= range
}
But now you realize that you also want to avoid targeting ships if they are
too close to you. We can update our visualization to illustrate the new
situation in Figure 2.3, where we want to target only those enemies that
are at least minimumDistance away from our current position:
As a result, we need to modify our code again:
let minimumDistance: Distance = 2.0
range
ownPosition.y
ownPosition.x
range
minD
Friendly
Finally, you also need to avoid targeting ships that are too close to one of
your other ships. Once again, we can visualize this in Figure 2.4:
Correspondingly, we can add a further argument that represents the
location of a friendly ship to our inRange function:
func inRange4(target: Position, ownPosition: Position,
friendly: Position, range: Distance) -> Bool {
let dx = ownPosition.x - target.x
let dy = ownPosition.y - target.y
let targetDistance = sqrt(dx * dx + dy * dy)
let friendlyDx = friendly.x - target.x
let friendlyDy = friendly.y - target.y
range
minD
Friendly
First-Class Functions
There are different approaches to refactoring this code. One obvious pattern would be to introduce a function that computes the distance between
two points, or functions that check when two points are close or far
away (or some definition of close and far away). In this chapter, however,
well take a slightly different approach.
The original problem boiled down to defining a function that determined when a point was in range or not. The type of such a function would
be something like:
func pointInRange(point: Position) -> Bool {
// Implement method here
}
From now on, the Region type will refer to functions from a Position to a
Bool. This isnt strictly necessary, but it can make some of the type signatures that well see below a bit easier to digest.
Instead of defining an object or struct to represent regions, we
represent a region by a function that determines if a given point is in
the region or not. If youre not used to functional programming, this
may seem strange, but remember: functions in Swift are first-class
values! We consciously chose the name Region for this type, rather
than something like CheckInRegion or RegionBlock. These names suggest
that they denote a function type, yet the key philosophy underlying
functional programming is that functions are values, no different from
structs, integers, or booleans using a separate naming convention for
functions would violate this philosophy.
We will now write several functions that create, manipulate, and combine regions. The first region we define is a circle, centered around the
origin:
Note that, given a radius r, the call circle(r) returns a function. Here we
use Swifts notation for closures to construct the function that we wish
to return. Given an argument position, point, we check that the point is
in the region delimited by a circle of the given radius centered around the
origin.
Of course, not all circles are centered around the origin. We could
add more arguments to the circle function to account for this. Instead,
though, we will write a region transformer:
func shift(offset: Position, region: Region) -> Region {
return { point in
let shiftedPoint = Position(x: point.x + offset.x,
y: point.y + offset.y)
return region(shiftedPoint)
}
}
The call shift(offset, region) moves the region to the right and up by
offet.x and offset.y, respectively. How is it implemented? Well, we need
to return a Region, which is a function from a point to a boolean value. To
do this, we start writing another closure, introducing the point we need
to check. From this point, we compute a new point with the coordinates
point.x + offset.x and point.y + offset.y. Finally, we check that this
new point is in the original region by passing it as an argument to the
region function.
Interestingly, there are lots of other ways to transform existing regions.
For instance, we may want to define a new region by inverting a region. The
resulting region consists of all the points outside the original region:
func invert(region: Region) -> Region {
return { point in !region(point) }
}
We can also write functions that combine existing regions into larger, complex regions. For instance, these two functions take the points that are in
both argument regions or either argument region, respectively:
func intersection(region1: Region, region2: Region) -> Region {
return { point in region1(point) && region2(point) }
}
func union(region1: Region, region2: Region) -> Region {
return { point in region1(point) || region2(point) }
}
This example shows how Swift lets you compute and pass around functions no differently than integers or booleans.
Now lets turn our attention back to our original example. With this
small library in place, we can now refactor the complicated inRange function as follows:
func inRange(ownPosition: Position, target: Position,
friendly: Position, range: Distance) -> Bool {
let rangeRegion = difference(circle(range), circle(minimumDistance))
let targetRegion = shift(ownPosition, rangeRegion)
let friendlyRegion = shift(friendly, circle(minimumDistance))
let resultRegion = difference(targetRegion, friendlyRegion)
return resultRegion(target)
}
This code defines two regions: targetRegion and friendlyRegion. The region that were interested in is computed by taking the difference between
these regions. By applying this region to the target argument, we can compute the desired boolean.
The way weve defined the Region type does have its disadvantages.
In particular, we cannot inspect how a region was constructed: Is it composed of smaller regions? Or is it simply a circle around the origin? The
only thing we can do is to check whether a given point is within a region
or not. If we would want to visualize a region, we would have to sample
enough points to generate a (black and white) bitmap.
In later chapters, we will sketch an alternative design that will allow
you to answer these questions.
Type-Driven Development
In the introduction, we mentioned how functional programs take the application of functions to arguments as the canonical way to assemble bigger programs. In this chapter, we have seen a concrete example of this
functional design methodology. We have defined a series of functions for
describing regions. Each of these functions is not very powerful by itself.
Yet together, they can describe complex regions that you wouldnt want
to write from scratch.
The solution is simple and elegant. It is quite different from what you
might write, had you just refactored the inRange4 function into separate
methods. The crucial design decision we made was how to define regions.
Once we chose the Region type, all the other definitions followed naturally.
The moral of the example is choose your types carefully. More than anything else, types guide the development process.
Notes
The code presented here is inspired by the Haskell solution to a problem
posed by the United States Advanced Research Projects Agency (ARPA) by
Chapter 3
Building Filters
Now that we have the Filter type defined, we can start defining functions that build specific filters. These are convenience functions that take
the parameters needed for a specific filter and construct a value of type
Filter. These functions will all have the following general shape:
func myFilter(/* parameters */) -> Filter
Note that the return value, Filter, is a function as well. Later on, this will
help us compose multiple filters to achieve the image effects we want.
To make our lives a bit easier, well extend the CIFilter class with a
convenience initializer and a computed property to retrieve the output image:
typealias Parameters = Dictionary<String, AnyObject>
extension CIFilter {
convenience init(name: String, parameters: Parameters) {
self.init(name: name)
setDefaults()
for (key, value: AnyObject) in parameters {
setValue(value, forKey: key)
}
}
var outputImage: CIImage {
return self.valueForKey(kCIOutputImageKey) as CIImage
}
}
The convenience initializer takes the name of the filter and a dictionary as
parameters. The key-value pairs in the dictionary will be set as parameters on the new filter object. Our convenience initializer follows the Swift
pattern of calling the designated initializer first.
The computed property, outputImage, provides an easy way to retrieve
the output image from the filter object. It looks up the value for the
kCIOutputImageKey key and casts the result to a value of type CIImage. By
providing this computed property of type CIImage, users of our API no
longer need to cast the result of such a lookup operation themselves.
Blur
With these pieces in place, we can define our first simple filters. The Gaussian blur filter only has the blur radius as its parameter:
func blur(radius: Double) -> Filter {
return { image in
let parameters: Parameters = [
kCIInputRadiusKey: radius,
kCIInputImageKey: image
]
let filter = CIFilter(name: "CIGaussianBlur",
parameters:parameters)
return filter.outputImage
}
}
Thats all there is to it. The blur function returns a function that
takes an argument image of type CIImage and returns a new image
(return filter.outputImage). Because of this, the return value of the
blur function conforms to the Filter type we have defined previously as
CIImage -> CIImage.
This example is just a thin wrapper around a filter that already exists
in Core Image. We can use the same pattern over and over again to create
our own filter functions.
Color Overlay
Lets define a filter that overlays an image with a solid color of our choice.
Core Image doesnt have such a filter by default, but we can, of course,
compose it from existing filters.
The two building blocks were going to use for this are the color generator filter (CIConstantColorGenerator) and the source-over compositing filter (CISourceOverCompositing). Lets first define a filter to generate a constant color plane:
func colorGenerator(color: NSColor) -> Filter {
return { _ in
let parameters: Parameters = [kCIInputColorKey: color]
let filter = CIFilter(name:"CIConstantColorGenerator",
parameters: parameters)
return filter.outputImage
}
}
This looks very similar to the blur filter weve defined above, with one notable difference: the constant color generator filter does not inspect its
input image. Therefore, we dont need to name the image parameter in
the function being returned. Instead, we use an unnamed parameter, _,
to emphasize that the image argument to the filter we are defining is ignored.
Next, were going to define the composite filter:
func compositeSourceOver(overlay: CIImage) -> Filter {
return { image in
let parameters: Parameters = [
kCIInputBackgroundImageKey: image,
kCIInputImageKey: overlay
]
let filter = CIFilter(name: "CISourceOverCompositing",
parameters: parameters)
let cropRect = image.extent()
return filter.outputImage.imageByCroppingToRect(cropRect)
}
}
Here we crop the output image to the size of the input image. This is not
strictly necessary, and it depends on how we want the filter to behave.
However, this choice works well in the examples we will cover.
Finally, we combine these two filters to create our color overlay filter:
func colorOverlay(color: NSColor) -> Filter {
return { image in
let overlay = colorGenerator(color)(image)
return compositeSourceOver(overlay)(image)
}
}
Once again, we return a function that takes an image parameter as its argument. The colorOverlay starts by calling the colorGenerator filter. The
colorGenerator filter requires a color as its argument and returns a filter, hence the code snippet colorGenerator(color) has type Filter. The
Filter type, however, is itself a function from CIImage to CIImage; we can
pass an additional argument of type CIImage to colorGenerator(color) to
compute a new overlay CIImage. This is exactly what happens in the definition of overlay we create a filter using the colorGenerator function
and pass the image argument to this filter to create a new image. Similarly, the value returned, compositeSourceOver(overlay)(image), consists
of a filter, compositeSourceOver(overlay), being constructed and subsequently applied to the image argument.
Composing Filters
Now that we have a blur and a color overlay filter defined, we can put them
to use on an actual image in a combined way: first we blur the image, and
then we put a red overlay on top. Lets load an image to work on:
let url = NSURL(string: "http://tinyurl.com/m74sldb");
let image = CIImage(contentsOfURL: url)
Function Composition
Of course, we could simply combine the two filter calls in the above code
in a single expression:
let result = colorOverlay(overlayColor)(blur(blurRadius)(image))
However, this becomes unreadable very quickly with all these parentheses involved. A nicer way to do this is to compose filters by defining a
custom operator for filter composition. To do so, well start by defining a
function that composes filters:
The composeFilters function takes two argument filters and defines a new
filter. This composite filter expects an argument img of type CIImage, and
passes it through both filter1 and filter2, respectively. We can use function composition to define our own composite filter, like this:
let myFilter1 = composeFilters(blur(blurRadius),
colorOverlay(overlayColor))
let result1 = myFilter1(image)
We can go one step further to make this even more readable, by introducing an operator for filter composition. Granted, defining your own operators all over the place doesnt necessarily contribute to the readability
of your code. However, filter composition is a recurring task in an image
processing library, so it makes a lot of sense:
infix operator >>> { associativity left }
func >>> (filter1: Filter, filter2: Filter) -> Filter {
return { img in filter2(filter1(img)) }
}
Now we can use the >>> operator in the same way we used the
composeFilters before:
let myFilter2 = blur(blurRadius) >>> colorOverlay(overlayColor)
let result2 = myFilter2(image)
The add1 function takes two integer arguments and returns their sum. In
Swift, however, we can also define another version of the same function:
func add2(x: Int) -> (Int -> Int) {
return { y in return x + y }
}
Here, the function add2 takes one argument, x, and returns a closure, expecting a second argument, y. These two add functions must be invoked
differently:
add1(1, 2)
add2(1)(2)
> 3
In the first case, we pass both arguments to add1 at the same time; in the
second case, we first pass the first argument, 1, which returns a function,
which we then apply to the second argument, 2. Both versions are equivalent: we can define add1 in terms of add2, and vice versa.
In Swift, we can even leave out one of the return statements and some
of the parentheses in the type signature of add2, and write:
The function arrow, ->, associates to the right. That is to say, you can read
the type A -> B -> C as A -> (B -> C). Throughout this book, however,
we will typically introduce a type alias for functional types (as we did for
the Region and Filter types), or write explicit parentheses.
The add1 and add2 examples show how we can always transform a function that expects multiple arguments into a series of functions that each
expect one argument. This process is referred to as currying, named after
the logician Haskell Curry; we say that add2 is the curried version of add1.
There is a third way to curry functions in Swift. Instead of constructing
the closure explicitly, as we did in the definition of add2, we can also define
a curried version of add1 as follows:
func add3(x: Int)(y: Int) -> Int {
return x + y
}
Here we have listed the arguments that add3 expects, one after the other,
each surrounded by its own parentheses. To call add3 we must, however,
provide an explicit name for the second argument:
add3(1)(y: 2)
So why is currying interesting? As we have seen in this book thus far, there
are scenarios where you want to pass functions as arguments to other
functions. If we have uncurried functions, like add1, we can only apply a
function to both its arguments. On the other hand, for a curried function,
like add2, we have a choice: we can apply it to one or two arguments. The
functions for creating filters that we have defined in this chapter have all
been curried they all expected an additional image argument. By writing our filters in this style, we were able to compose them easily using the
>>> operator.
Discussion
This example illustrates, once again, how we break complex code into
small pieces, which can all be reassembled using function application.
The goal of this chapter was not to define a complete API around Core
Image, but instead to sketch out how higher-order functions and function
composition can be used in a more practical case study.
Why go through all this effort? Its true that the Core Image API is already mature and provides all the functionality you might need. But in
spite of this, we believe there are several advantages to the API designed
in this chapter:
Safety using the API we have sketched, it is almost impossible to
create runtime errors arising from undefined keys or failed casts.
Modularity it is easy to compose filters using the >>> operator.
Doing so allows you to tease apart complex filters into smaller, simpler, reusable components. Additionally, composed filters have the
exact same type as their building blocks, so you can use them interchangeably.
Clarity even if you have never used Core Image, you should be
able to assemble simple filters using the functions we have defined.
To access the results, you dont need to know about special dictionary keys, such as kCIOutputImageKey, or worry about initializing
certain keys, such as kCIInputImageKey or kCIInputRadiusKey. From
the types alone, you can almost figure out how to use the API, even
without further documentation.
Our API presents a series of functions that can be used to define and compose filters. Any filters that you define are safe to use and reuse. Each
filter can be tested and understood in isolation. We believe these are compelling reasons to favor the design sketched here over the original Core
Image API.
Chapter 4
Functions that take functions as arguments are sometimes called higherorder functions. In this chapter, we will tour some of the higher-order functions on arrays from the Swift standard library. By doing so, we will introduce Swifts generics and show how to assemble complex computations
on arrays.
Introducing Generics
Suppose we need to write a function that, given an array of integers, computes a new array, where every integer in the original array has been incremented by one. Such a function is easy to write using a single for loop:
func incrementArray(xs: [Int]) -> [Int] {
var result: [Int] = []
for x in xs {
result.append(x + 1)
}
return result
}
Now suppose we also need a function that computes a new array, where
every element in the argument array has been doubled. This is also easy
to do using a for loop:
func doubleArray1(xs: [Int]) -> [Int] {
var result: [Int] = []
for x in xs {
result.append(x * 2)
}
return result
}
Both of these functions share a lot of code. Can we abstract over the differences and write a single, more general function that captures this pattern? Such a function would look something like this:
func computeIntArray(xs: [Int]) -> [Int] {
var result: [Int] = []
for x in xs {
result.append(/* something using x */)
}
return result
}
Now we can pass different arguments, depending on how we want to compute a new array from the old array. The doubleArray and incrementArray
functions become one-liners that call computeIntArray:
func doubleArray2(xs: [Int]) -> [Int] {
return computeIntArray(xs) { x in x * 2 }
}
Note that we are using Swifts syntax for trailing closures here we provide the final (closure) argument to computeIntArray after the parentheses
containing the other arguments.
This code is still not as flexible as it could be. Suppose we want to
compute a new array of booleans, describing whether the numbers in the
original array were even or not. We might try to write something like this:
func isEvenArray(xs: [Int]) -> [Bool] {
computeIntArray(xs) { x in x % 2 == 0 }
}
Unfortunately, this code gives a type error. The problem is that our
computeIntArray function takes an argument of type Int -> Int, that is, a
function that returns an integer. In the definition of isEvenArray, we are
passing an argument of type Int -> Bool, which causes the type error.
How should we solve this? One thing we could do is define a new
version of computeIntArray that takes a function argument of type
Int -> Bool. That might look something like this:
func computeBoolArray(xs: [Int], f: Int -> Bool) -> [Bool] {
let result: [Bool] = []
for x in xs {
result.append(f(x))
}
return result
}
This doesnt scale very well though. What if we need to compute a String
next? Do we need to define yet another higher-order function, expecting
an argument of type Int -> String?
Luckily, there is a solution to this problem: we can use generics. The
definitions of computeBoolArray and computeIntArray are identical; the only
difference is in the type signature. If we were to define another version,
computeStringArray, the body of the function would be the same again. In
fact, the same code will work for any type. What we really want to do is
write a single generic function that will work for every possible type:
func genericComputeArray<U>(xs: [Int], f: Int -> U) -> [U] {
var result: [U] = []
for x in xs {
result.append(f(x))
}
return result
}
The most interesting thing about this piece of code is its type signature. To understand this type signature, it may help you to think of
genericComputeArray<U> as a family of functions. Each choice of the type
variable U determines a new function. This function takes an array of
integers and a function of type Int -> U as arguments, and returns an
array of type [U].
We can generalize this function even further. There is no reason for it
to operate exclusively on input arrays of type [Int]. Abstracting over this
yields the following type signature:
func map<T, U>(xs: [T], f: T -> U) -> [U] {
var result: [U] = []
for x in xs {
result.append(f(x))
}
return result
}
Once again, the definition of the function is not that interesting: given
two arguments, xs and f, apply map to (xs, f), and return the result.
The types are the most interesting thing about this definition. The
genericComputeArray is an instance of the map function, only it has a more
specific type.
There is already a map method defined in the Swift standard library in
the array type. Instead of writing map(xs, f), we can call Arrays map function by writing xs.map(f). Here is an example definition of the doubleArray
function, using Swifts built-in map function:
func doubleArray3(xs: [Int]) -> [Int] {
return xs.map { x in 2 * x }
}
The point of this chapter is not to argue that you should define map yourself;
we want to argue that there is no magic involved in the definition of map
you could have defined it yourself!
Filter
The map function is not the only function in Swifts standard array library
that uses generics. In the upcoming sections, we will introduce a few others.
Suppose we have an array containing strings, representing the contents of a directory:
Now suppose we want an array of all the .swift files. This is easy to compute with a simple loop:
func getSwiftFiles(files: [String]) -> [String] {
var result: [String] = []
for file in files {
if file.hasSuffix(".swift") {
result.append(file)
}
}
return result
}
We can now use this function to ask for the Swift files in our exampleFiles
array:
getSwiftFiles(exampleFiles)
for x in xs {
if check(x) {
result.append(x)
}
}
return result
}
Just like map, the array type already has a filter function defined in
Swifts standard library. We can call Swifts built-in filter function on
our exampleFiles array, as follows:
exampleFiles.filter { file in file.hasSuffix(".swift") }
Now you might wonder: is there an even more general purpose function
that can be used to define both map and filter? In the last part of this
chapter, we will answer that question.
Reduce
Once again, we will consider a few simple functions before defining a
generic function that captures a more general pattern.
It is straightforward to define a function that sums all the integers in
an array:
func sum(xs: [Int]) -> Int {
var result: Int = 0
for x in xs {
result += x
}
return result
}
We can use this sum function to compute the sum of all the integers in an
array:
let xs = [1, 2, 3, 4]
sum(xs)
> 10
A similar for loop computes the product of all the integers in an array:
func product(xs: [Int]) -> Int {
var result: Int = 1
for x in xs {
result = x * result
}
return result
}
}
return result
}
What do all these functions have in common? They all initialize a variable,
result, with some value. They proceed by iterating over all the elements
of the input array, xs, updating the result somehow. To define a generic
function that can capture this pattern, there are two pieces of information that we need to abstract over: the initial value assigned to the result
variable, and the function used to update the result in every iteration.
With this in mind, we arrive at the following definition for the reduce
function that captures this pattern:
func reduce<A, R>(arr: [A],
initialValue: R,
combine: (R, A) -> R) -> R {
var result = initialValue
for i in arr {
result = combine(result, i)
}
return result
}
The type of reduce is a bit hard to read at first. It is generic in two ways:
for any input array of type [A], it will compute a result of type R. To do
this, it needs an initial value of type R (to assign to the result variable),
and a function, combine: (R, A) -> R, which is used to update the result
variable in the body of the for loop. In some functional languages, such as
OCaml and Haskell, reduce functions are called fold or fold_right.
We can define every function we have seen in this chapter thus far us-
Instead of writing a closure, we could have also written just the operator
as the last argument. This makes the code even shorter:
func productUsingReduce(xs: [Int]) -> Int {
return reduce(xs, 1, *)
}
func concatUsingReduce(xs: [String]) -> String {
return reduce(xs, "", +)
}
This shows how the reduce function captures a very common programming
pattern: iterating over an array to compute a result.
Now suppose we would like to print a list of cities with at least one million
inhabitants, together with their total populations. We can define a helper
function that scales up the inhabitants:
func scale(city: City) -> City {
return City(name: city.name, population: city.population * 1000)
}
Now we can use all the ingredients we have seen in this chapter to write
the following statement:
cities.filter({ city in city.population > 1000 })
.map(scale)
.reduce("City: Population") { result, c in
return result + "\n" + "\(c.name) : \(c.population)"
}
We start by filtering out those cities that have less than one million inhabitants. We then map our scale function over the remaining cities. Finally,
we compute a String with a list of city names and populations, using the
reduce function. Here we use the map, filter, and reduce definitions from
the Array type in Swifts standard library. As a result, we can chain together the results of our maps and filters nicely. The cities.filter(..)
expression computes an array, on which we call map; we call reduce on the
result of this call to obtain our final result.
the Any type and generics can be used to define functions accepting different types of arguments. However, it is very important to understand the
difference: generics can be used to define flexible functions, the types of
which are still checked by the compiler; the Any type can be used to dodge
Swifts type system (and should be avoided whenever possible).
Lets consider the simplest possible example, which is a function that
does nothing but return its argument. Using generics, we might write the
following:
func noOp<T>(x: T) -> T {
return x
}
Both noOp and noOpAny will accept any (non-functional) argument. The crucial difference is what we know about the value being returned. In the
definition of noOp, we can clearly see that the return value is the same as
the input value. This is not the case for noOpAny, which may return a value
of any type even a type different from the original input. We might also
give the following, erroneous definition of noOpAny:
func noOpAnyWrong(x: Any) -> Any {
return 0
}
Using the Any type evades Swifts type system. However, trying to return 0
in the body of the noOp function defined using generics will cause a type error. Furthermore, any function that calls noOpAny does not know to which
type the result must be cast. There are all kinds of possible runtime exceptions that may be raised as a result.
Finally, the type of a generic function is extremely informative. Consider the following generic version of the function composition operator,
Notes
The history of generics traces back to Strachey (2000), Girards System F
(1972), and Reynolds (1974). Note that these authors refer to generics as
(parametric) polymorphism, a term that is still used in many other functional languages. Many object-oriented languages use the term polymorphism to refer to implicit casts arising from subtyping, so the term generics was introduced to disambiguate between the two concepts.
The process that we sketched informally above, motivating
why there can only be one possible function with the generic type
(f: A -> B, g: B -> C) -> A -> C, can be made mathematically precise.
This was first done by Reynolds (1983); later Wadler (1989) referred to this
as Theorems for free! emphasizing how you can compute a theorem
about a generic function from its type.
Chapter 5
Optionals
Swifts optional types can be used to represent values that may be missing or computations that may fail. This chapter describes Swifts optional
types, how to work with them effectively, and how they fit well within the
functional programming paradigm.
keys and stored values, respectively. In our example, the city dictionary
has type Dictionary<String, Int>. There is also a shorthand notation,
[String: Int].
We can look up the value associated with a key using the same notation
as array indexing:
let madridPopulation: Int = cities["Madrid"]
This example, however, does not type check. The problem is that the key
"Madrid" may not be in the cities dictionary and what value should be
returned if it is not? We cannot guarantee that the dictionary lookup operation always returns an Int for every key. Swifts optional types track the
possibility of failure. The correct way to write the example above would
be:
let madridPopulation: Int? = cities["Madrid"]
Instead of having type Int, the madridPopulation example has the optional
type Int?. A value of type Int? is either an Int or a special missing value,
nil.
We can check whether or not the lookup was successful:
if madridPopulation != nil {
println("The population of Madrid is " +
"\(madridPopulation! * 1000)")
} else {
println("Unknown city: Madrid")
}
madridPopulation: Int
infix operator ??
func ??<T>(optional: T?, defaultValue: T) -> T {
if let x = optional {
return x
} else {
return defaultValue
}
}
The ?? operator checks whether or not its optional argument is nil. If it is,
it returns its defaultValue argument; otherwise, it returns the optionals
underlying value.
There is one problem with this definition: the defaultValue may be evaluated, regardless of whether or not the optional is nil. This is usually un-
() -> T.
myOptional ?? { myDefaultValue }
The definition in the Swift standard library avoids the need for creating explicit closures by using Swifts autoclosure type attribute. This implicitly
wraps any arguments to the ?? operator in the required closure. As a result, we can provide the same interface that we initially had, but without
requiring the user to create an explicit closure wrapping the defaultValue
argument. The actual definition used in Swifts standard library is as follows:
infix operator ?? { associativity right precedence 110 }
func ??<T>(optional: T?,
defaultValue: @autoclosure () -> T) -> T {
if let x = optional {
return x
} else {
return defaultValue()
}
}
The ?? provides a safer alternative to the forced optional unwrapping, without being as verbose as the optional binding.
Optional Chaining
First of all, Swift has a special mechanism, optional chaining, for selecting
methods or attributes in nested classes or structs. Consider the following
(fragment of a) model for processing customer orders:
struct Order {
let orderNumber: Int
let person: Person?
// ...
}
struct Person {
let name: String
let address: Address?
// ...
}
struct Address {
let streetName: String
Given an Order, how can we find the state of the customer? We could use
the explicit unwrapping operator:
order.person!.address!.state!
Doing so, however, may cause runtime exceptions if any of the intermediate data is missing. It would be much safer to use option binding:
if let myPerson = order.person in {
if let myAddress = myPerson.address in {
if let myState = myAddress.state in {
// ...
But this is rather verbose. Using optional chaining, this example would
become:
if let myState = order.person?.address?.state? {
print("This order will be shipped to \(myState\)")
} else {
print("Unknown person, address, or state.")
}
Instead of forcing the unwrapping of intermediate types, we use the question mark operator to try and unwrap the optional types. When any of the
component selections fails, the whole chain of selection statements returns nil.
This map function takes two arguments: an optional value of type T?, and
a function f of type T -> U. If the optional value is not nil, it applies f to
it and returns the result; otherwise, the map function returns nil. This map
function is part of the Swift standard library.
Using map, we write the incrementOptional function as:
func incrementOptional2(optional: Int?) -> Int? {
return optional.map { x in x + 1 }
}
Of course, we can also use map to project fields or methods from optional
structs and classes, similar to the ? operator.
Why is this function called map? What does it have to do with array computations? There is a good reason for calling both of these functions map,
but we will defer this discussion for the moment. In Chapter 14, we will
explain the relation in greater detail.
This program is not accepted by the Swift compiler. Can you spot the error?
The problem is that addition only works on Int values, rather than the
optional Int? values we have here. To resolve this, we would have to introduce nested if statements, as follows:
func addOptionals(optionalX: Int?, optionalY: Int?) -> Int? {
if let x = optionalX {
if let y = optionalY {
return x + y
}
}
return nil
}
This may seem like a contrived example, but manipulating optional values
can happen all the time. Suppose we have the following dictionary, associating countries with their capital cities:
let capitals = ["France": "Paris", "Spain": "Madrid",
"The Netherlands": "Amsterdam",
"Belgium": "Brussels"]
In order to write a function that returns the number of inhabitants for the
capital of a given country, we use the capitals dictionary in conjunction
with the cities dictionary defined previously. For each dictionary lookup,
we have to make sure that it actually returned a result:
func populationOfCapital(country: String) -> Int? {
if let capital = capitals[country] {
if let population = cities[capital] {
return population * 1000
}
}
return nil
}
The >>= operator checks whether some optional value is non-nil. If it is,
we pass it on to the argument function f; if the optional argument is nil,
the result is also nil.
Using this operator, we can now write our examples as follows:
func addOptionals2(optionalX: Int?, optionalY: Int?) -> Int? {
return optionalX >>= { x in
optionalY >>= { y in
x + y
}
}
}
func populationOfCapital2(country: String) -> Int? {
return capitals[country] >>= { capital in
cities[capital] >>= { population in
return population * 1000
}
}
}
We do not want to advocate that >>= is the right way to combine optional
values. Instead, we hope to show that optional binding is not magically
built-in to the Swift compiler, but rather a control structure you can implement yourself using a higher-order function.
Why Optionals?
Whats the point of introducing an explicit optional type? For programmers used to Objective-C, working with optional types may seem strange
at first. The Swift type system is rather rigid: whenever we have an optional type, we have to deal with the possibility of it being nil. We have had
to write new functions like map to manipulate optional values. In ObjectiveC, you have more flexibility. For instance, when translating the example
above to Objective-C, there is no compiler error:
- (int)populationOfCapital:(NSString *)country
{
return [self.cities[self.capitals[country]] intValue] * 1000;
}
We can pass in nil for the name of a country, and we get back a result of
0.0. Everything is fine. In many languages without optionals, null pointers
are a source of danger. Much less so in Objective-C. In Objective-C, you
can safely send messages to nil, and depending on the return type, you
either get nil, 0, or similar zero-like values. Why change this behavior
in Swift?
The choice for an explicit optional type fits with the increased static
safety of Swift. A strong type system catches errors before code is executed, and an explicit optional type helps protect you from unexpected
crashes arising from missing values.
The default zero-like behavior employed by Objective-C has its drawbacks. You may want to distinguish between failure (a key is not in the
dictionary) and success-returning nil (a key is in the dictionary, but associated with nil). To do that in Objective-C, you have to use NSNull.
While it is safe in Objective-C to send messages to nil, it is often not
safe to use them. Lets say we want to create an attributed string. If we
pass in nil as the argument for country, the capital will also be nil, but
NSAttributedString will crash when trying to initialize it with a nil value:
- (NSAttributedString *)attributedCapital:(NSString *)country
{
NSString *capital = self.capitals[country];
NSDictionary *attributes = @{ /* ... */ };
return [[NSAttributedString alloc] initWithString:capital
attributes:attributes];
}
While crashes like that dont happen too often, almost every developer has
had code like this crash. Most of the time, these crashes are detected during debugging, but it is very possible to ship code without noticing that, in
some cases, a variable might unexpectedly be nil. Therefore, many programmers use asserts to verify this behavior. For example, we can add an
NSParameterAssert to make sure we crash quickly when the country is nil:
- (NSAttributedString *)attributedCapital:(NSString *)country
{
NSParameterAssert(country);
NSString *capital = self.capitals[country];
NSDictionary *attributes = @{ /* ... */ };
return [[NSAttributedString alloc] initWithString:capital
attributes:attributes];
}
Now, when we pass in a country value that is nil, the assert fails immediately, and we are almost certain to hit this during debugging. But
what if we pass in a country value that doesnt have a matching key in
self.capitals? This is much more likely, especially when country comes
from user input. In that case, capital will be nil and our code will still
crash. Of course, this can be fixed easily enough. The point is, however,
that it is easier to write robust code using nil in Swift than in Objective-C.
Finally, using these assertions is inherently non-modular. Suppose
we implement a checkCountry method that checks that a non-empty
NSString* is supported. We can incorporate this check easily enough:
- (NSAttributedString *)attributedCapital:(NSString*)country
{
NSParameterAssert(country);
if (checkCountry(country)) {
// ...
}
}
Now the question arises: should the checkCountry function also assert
that its argument is non-nil? On one hand, it should not: we have just
performed the check in the attributedCapital method. On the other hand,
if the checkCountry function only works on non-nil values, we should duplicate the assertion. We are forced to choose between exposing an unsafe
interface or duplicating assertions.
In Swift, things are a bit better. Function signatures using optionals explicitly state which values may be nil. This is invaluable information when
working with other peoples code. A signature like the following provides
a lot of information:
func attributedCapital(country: String) -> NSAttributedString?
Not only are we warned about the possibility of failure, but we know that
we must pass a String as argument and not a nil value. A crash like the
one we described above will not happen. Furthermore, this is information
checked by the compiler. Documentation goes out of date easily; you can
always trust function signatures.
Chapter 6
QuickCheck
Now, if we run QuickCheck on this function, we will get a failing test case:
check("Minus should be commutative", minusIsCommutative)
Using Swifts syntax for trailing closures, we can also write tests directly, without defining the property (such as plusIsCommutative or
minusIsCommutative) separately:
check("Additive identity") { (x: Int) in x + 0 == x }
Building QuickCheck
In order to build our Swift implementation of QuickCheck, we will need to
do a couple of things.
First, we need a way to generate random values for different types.
Using these random value generators, we need to implement the
check function, which passes random values to its argument property.
If a test fails, we would like to make the test input as small as possible. For example, if our test fails on an array with 100 elements,
well try to make it smaller and see if the test still fails.
Finally, well need to do some extra work to make sure our check
function works on types that have generics.
So lets write an instance for Int. We use the arc4random function from the
standard library and convert it into an Int. Note that this only generates
positive integers. A real implementation of the library would generate negative integers as well, but well try to keep things simple in this chapter:
extension Int: Arbitrary {
static func arbitrary() -> Int {
return Int(arc4random())
}
}
> 2158783973
We use the tabulate function to fill an array with the numbers from 0 to
times-1. By using the map function, we then generate an array with the
values f(0), f(1), , f(times-1). The arbitrary extension to String uses
the tabulate function to populate an array of random characters.
We can call it in the same way as we generate random Ints, except that
we call it on the String class:
String.arbitrary()
> XMVDXQEIRYNRJTWELHESXHIGPSPOFETEEX
We could have chosen to use a more functional style by writing this function using reduce or map, rather than a for loop. In this example, however,
for loops make perfect sense: we want to iterate an operation a fixed number of times, stopping execution once a counterexample has been found
and for loops are perfect for that.
Heres how we can use this function to test properties:
func area(size: CGSize) -> CGFloat {
return size.width * size.height
}
check1("Area should be at least 0") { size in area(size) >= 0 }
Here we can see a good example of when QuickCheck can be very useful:
it finds an edge case for us. If a size has exactly one negative component,
our area function will return a negative number. When used as part of
a CGRect, a CGSize can have negative values. When writing ordinary unit
tests, it is easy to oversee this case, because sizes usually only have positive components.
Note that the return type of the smaller function is marked as optional.
There are cases when it is not clear how to shrink test data any further.
For example, there is no obvious way to shrink an empty array. We will
return nil in that case.
In our instance, for integers, we just try to divide the integer by two
until we reach zero:
extension Int: Smaller {
func smaller() -> Int? {
return self == 0 ? nil : self / 2
}
}
> Optional(50)
For strings, we just drop the first character (unless the string is empty):
extension String: Smaller {
func smaller() -> String? {
return self.isEmpty ? nil
: dropFirst(self)
}
}
To use the Smaller protocol in the check function, we will need the ability
to shrink any test data generated by our check function. To do so, we will
redefine our Arbitrary protocol to extend the Smaller protocol:
protocol Arbitrary: Smaller {
class func arbitrary() -> Self
}
Repeatedly Shrinking
We can now redefine our check function to shrink any test data that triggers a failure. To do this, we use the iterateWhile function, which takes
a condition and an initial value, and repeatedly applies a function as long
as the condition holds:
func iterateWhile<A>(condition: A -> Bool,
initialValue: A,
next: A -> A?) -> A {
if let x = next(initialValue) {
if condition(x) {
return iterateWhile(condition, x, next)
}
}
return initialValue
}
This function is doing quite a bit: generating random input values, checking whether they satisfy the property argument, and repeatedly shrinking
a counterexample, once one is found. One advantage of defining the repeated shrinking using iterateWhile, rather than a separate while loop, is
that the control flow of this piece of code stays reasonably simple.
Arbitrary Arrays
Currently, our check2 function only supports Int and String values. While
we are free to define new extensions for other types, such as Bool, things
get more complicated when we want to generate arbitrary arrays. As a
motivating example, lets write a functional version of QuickSort:
func qsort(var array: [Int]) -> [Int] {
if array.isEmpty { return [] }
let pivot = array.removeAtIndex(0)
let lesser = array.filter { $0 < pivot }
let greater = array.filter { $0 >= pivot }
return qsort(lesser) + [pivot] + qsort(greater)
}
Arbitrary
return Array(dropFirst(self))
}
return nil
}
}
We can also write a function that generates an array of arbitrary length for
any type that conforms to the Arbitrary protocol:
func arbitraryArray<X: Arbitrary>() -> [X] {
let randomLength = Int(arc4random() % 50)
return tabulate(randomLength) { _ in return X.arbitrary() }
}
If we have a type for which we cannot define the desired Arbitrary instance, as is the case with arrays, we can overload the check function and
construct the desired ArbitraryI struct ourselves:
func check<X: Arbitrary>(message: String,
prop: [X] -> Bool) -> () {
let instance = ArbitraryI(arbitrary: arbitraryArray,
smaller: { (x: [X]) in x.smaller() })
checkHelper(instance, prop, message)
}
Now, we can finally run check to verify our QuickSort implementation. Lots
of random arrays will be generated and passed to our test:
check("qsort should behave like sort") { (x: [Int]) in
return qsort(x) == x.sorted(<)
}
Using QuickCheck
Somewhat counterintuitively, there is strong evidence to suggest that
testing technology influences the design of your code. People who rely
on test-driven design use tests not only to verify that their code is correct.
Instead, they also report that by writing your code in a test-driven fashion,
the design of the code gets simpler. This makes sense if it is easy to
write a test for a class without having a complicated setup procedure, it
means that the class is nicely decoupled.
For QuickCheck, the same rules apply. It will often not be easy to take
existing code and add QuickCheck tests as an afterthought, particularly
when you have an existing object-oriented architecture that relies heavily
on other classes or makes use of mutable state. However, if you start
by doing test-driven development using QuickCheck, you will see that
it strongly influences the design of your code. QuickCheck forces you
to think of the abstract properties that your functions must satisfy and
allows you to give a high-level specification. A unit test can assert that
3 + 0 is equal to 0 + 3; a QuickCheck property states more generally
that addition is a commutative operation. By thinking about a high-level
QuickCheck specification first, your code is more likely to be biased
toward modularity and referential transparency (which we will cover in
the next chapter). QuickCheck does not work as well on stateful functions
or APIs. As a result, writing your tests up front with QuickCheck will help
keep your code clean.
Next Steps
This library is far from complete, but already quite useful. That said, there
are a couple of obvious things that could be improved upon:
The shrinking is naive. For example, in the case of arrays, we currently remove the first element of the array. However, we might also
choose to remove a different element, or make the elements of the
array smaller (or do all of that). The current implementation returns
Chapter 7
Swift has several mechanisms for controlling how values may change. In
this chapter, we will explain how these different mechanisms work, distinguish between value types and reference types, and argue why it is a
good idea to limit the usage of mutable state.
The crucial difference is that we can assign new values to variables declared using var, whereas variables created using let cannot change:
x = 3 // This is fine
y = 4 // This is rejected by the compiler
We will refer to variables declared using a let as immutable variables; variables declared using a var, on the other hand, are said to be mutable.
Why you might wonder would you ever declare an immutable variable? Doing so limits the variables capabilities. A mutable variable is
strictly more versatile. There is a clear case for preferring var over let.
Yet in this section, we want to try and argue that the opposite is true.
Imagine having to read through a Swift class that someone else has
written. There are a few methods that all refer to an instance variable with
some meaningless name, say x. Given the choice, would you prefer x to
be declared with a var or a let? Clearly declaring x to be immutable is
preferable: you can read through the code without having to worry about
what the current value of x is, youre free to substitute x for its definition,
and you cannot invalidate x by assigning it some value that might break
invariants on which the rest of the class relies.
Immutable variables may not be assigned a new value. As a result, it
is easier to reason about immutable variables. In his famous paper, Go
To Statement Considered Harmful, Edgar Dijkstra writes:
My remark is that our intellectual powers are rather geared
to master static relations and that our powers to visualize processes evolving in time are relatively poorly developed.
Dijkstra goes on to argue that the mental model a programmer needs to
develop when reading through structured code (using conditionals, loops,
and function calls, but not goto statements) is simpler than spaghetti
code full of gotos. We can take this discipline even further and eschew
the use of mutable variables: var considered harmful.
var x: Int
var y: Int
}
What are the values of structpoint and sameStructPoint after executing this code?
Clearly, sameStructPoint should be equal to
PointStruct(x: 3, y: 2). But what about structPoint? Does the assignment to sameStructPoint modify the original structPoint? This is
where the distinction between value types and reference types is important: when assigned to a new variable, such as sameStructPoint, value
types are copied. In this example, the assignment to sameStructPoint.x
does not update the original strutPoint, because structs are reference
types. We could, instead, declare a class for points:
class PointClass {
var x: Int
var y: Int
init(x: Int, y: Int) {
self.x = x
self.y = y
}
}
Then we can adapt our code fragment to use this class instead:
var classPoint = PointClass(x: 1, y: 2)
var sameClassPoint = classPoint
sameClassPoint.x = 3
What is the value of structPoint after this function call? Does the call
to setStructToOrigin modify the original structPoint or not? All value
types, such as structs, are copied when passed as function arguments.
In this example, the original structPoint is unmodified after the call to
setStructToOrigin.
Now suppose we had written the following function, operating on
classes rather than structs:
func setClassToOrigin(point: PointClass) -> PointClass {
point.x = 0
point.y = 0
return point
}
the existing object or instance is used. Any changes to this reference will
also mutate the original object or instance.
Andy Matuschak provides some very useful intuition for the difference
between value types and reference types in his article for objc.io.
Structs are not the only value type in Swift. In fact, almost all the
types in Swift are value types, including arrays, dictionaries, numbers,
booleans, tuples, and enums (the latter will be covered in the coming chapter). Classes are the exception, rather than the rule. This is one example
of how Swift is moving away from object-oriented programming in favor of
other programming paradigms.
We will discuss the relative merits of classes and structs later on in
this section; before we do so, we want to briefly discuss the interaction
between the different forms of mutability that we have seen thus far.
If we declare the x and y properties within the struct using the let keyword,
then we cant ever change them after initialization, no matter whether the
variable holding the point instance is mutable or immutable:
struct ImmutablePointStruct {
let x: Int
let y: Int
}
var immutablePoint2 = ImmutablePointStruct(x: 1, y: 1)
immutablePoint2.x = 3 // Rejected!
Objective-C
The concept of mutability and immutability should already be familiar to
many Objective-C programmers. Many of the data structures provided by
Apples Core Foundation and Foundation frameworks exist in immutable
and mutable variants, such as NSArray and NSMutableArray, NSString and
NSMutableString, and others. Using the immutable types is the default
choice in most cases, just as Swift favors value types over reference types.
In contrast to Swift, however, there is no foolproof way to enforce
immutability in Objective-C. We could declare the objects properties as
read-only (or only expose an interface that avoids mutation), but this
will not stop us from (unintentionally) mutating values internally after
they have been initialized. When working with legacy code, for instance,
it is all too easy to break assumptions about mutability that cannot be
enforced by the compiler. Without checks by the compiler, it is very hard
to enforce any kind of discipline in the use of mutable variables.
Discussion
In this chapter, we have seen how Swift distinguishes between mutable
and immutable values, and between value types and reference types. In
this final section, we want to explain why these are important distinctions.
When studying a piece of software, coupling measures the degree to
which individual units of code depend on one another. Coupling is one of
the single most important factors that determines how well software is
structured. In the worst case, all classes and methods refer to one another, sharing numerous mutable variables, or even relying on exact implementation details. Such code can be very hard to maintain or update:
instead of understanding or modifying a small code fragment in isolation,
you constantly need to consider the system in its totality.
In Objective-C and many other object-oriented languages, it is common for class methods to be coupled through shared instance variables.
As a result, however, mutating the variable may change the behavior of
the classs methods. Typically, this is a good thing once you change
the data stored in an object, all its methods may refer to its new value.
At the same time, however, such shared instance variables introduce coupling between all the classs methods. If any of these methods or some
external function invalidate the shared state, all the classs methods may
exhibit buggy behavior. It is much harder to test any of these methods in
isolation, as they are now coupled to one another.
Now compare this to the functions that we tested in the QuickCheck
chapter. Each of these functions computed an output value that only depended on the input values. Such functions that compute the same output
for equal inputs are sometimes called referentially transparent. By definition, referentially transparent methods are loosely coupled from their
environments: there are no implicit dependencies on any state or variables, aside from the functions arguments. Consequently, referentially
transparent functions are easier to test and understand in isolation. Furthermore, we can compose, call, and assemble functions that are referentially transparent without losing this property. Referential transparency
is a guarantee of modularity and reusability.
}
return result
}
The sum function uses a mutable variable, result, that is repeatedly updated. Yet the interface exposed to the user hides this fact. The sum function is still referentially transparent, and arguably easier to understand
than a convoluted definition avoiding mutable variables at all costs. This
example illustrates a benign usage of mutable state.
Such benign mutable variables have many applications. Consider the
qsort method defined in the QuickCheck chapter:
func qsort(var array: [Int]) -> [Int] {
if array.isEmpty { return [] }
let pivot = array.removeAtIndex(0)
let lesser = array.filter { $0 < pivot }
let greater = array.filter { $0 >= pivot }
return qsort(lesser) + [pivot] + qsort(greater)
}
Chapter 8
Enumerations
Throughout this book, we want to emphasize the important role types play
in the design and implementation of Swift applications. In this chapter,
we will describe Swifts enumerations, which enable you to craft precise
types representing the data your application uses.
Introducing Enumerations
When creating a string, it is important to know its character encoding. In
Objective-C, an NSString object can have several possible encodings:
enum NSStringEncoding {
NSASCIIStringEncoding = 1,
NSNEXTSTEPStringEncoding = 2,
NSJapaneseEUCStringEncoding = 3,
NSUTF8StringEncoding = 4,
// ...
}
Each of these encodings is represented by a number; the enum allows programmers to assign meaningful names to the integer constants associ-
This definition defines which value to return for each of our Encoding types.
Note that we have one branch for each of our four different encoding
schemes. If we leave any of these branches out, the Swift compiler warns
us that the toNSStringEncoding functions switch statement is not complete. Once again, this static check is not present in the enumerations
used in Objective-C.
Of course, we can also define a function that works in the opposite
direction, creating an Encoding from an NSStringEncoding:
func createEncoding(enc: NSStringEncoding) -> Encoding? {
switch enc {
case NSASCIIStringEncoding:
return Encoding.ASCII
case NSNEXTSTEPStringEncoding:
return Encoding.NEXTSTEP
case NSJapaneseEUCStringEncoding:
return Encoding.JapaneseEUC
case NSUTF8StringEncoding:
return Encoding.UTF8
default:
return nil
}
}
As we have not modeled all possible NSStringEncoding values in our little Encoding enumeration, the createEncoding function returns an optional
Encoding value. If none of the first four cases succeed, the default branch
is selected, which returns nil.
Of course, we do not need to use switch statements to work with our
Encoding enumeration. For example, if we want the localized name of an
encoding, we can compute it as follows:
func localizedEncodingName(encoding: Encoding) -> String {
return String.localizedNameOfStringEncoding(
toNSStringEncoding(encoding))
}
Associated Values
So far, we have seen how Swifts enumerations can be used to describe
a choice between several different alternatives. The Encoding enumeration provided a safe, typed representation of different string encoding
schemes. There are, however, many more applications of enumerations.
The readFile1 function returns an optional string. It simply calls the initializer, passing the address for an NSError object. If the call to the initializer
succeeds, the resulting string is returned; upon failure, the whole function
returns nil.
This interface is a bit more precise than Objective-Cs stringWithContentsOfFile
from the type alone, it is clear that this function may fail. There is no
temptation for developers to pass null pointers for the error argument,
ignoring the possibility of failure.
There is one drawback to using Swifts optional type: we no longer return the error message when the file cannot be read. This is rather unfortunate if a call to readFile1 fails, there is no way to diagnose what went
wrong. Does the file not exist? Is it corrupt? Or do you not have the right
permissions?
Ideally, we would like our readFile function to return either a String
or an NSError. Using Swifts enumerations, we can do just that. Instead
of returning a String?, we will redefine our readFile function to return a
member of the ReadFileResult enumeration. We can define this enumeration as follows:
enum ReadFileResult {
case Success(String)
case Failure(NSError)
}
Instead of returning an optional String, we now return either the file contents or an NSError. We first check if the string is non-nil and return the
value. If the string was nil, we check if there was an error opening the file
and, if so, return a Failure. Finally, if both maybeError and maybeString are
nil after the call to the initializer, something is very wrong indeed we
assert false and should try to give a meaningful error message.
Upon calling readFile, you can use a switch statement to determine
whether or not the function succeeded:
switch readFile("/Users/wouter/fpinswift/README.md", Encoding.ASCII) {
case let ReadFileResult.Success(contents):
println("File succesfully opened..")
case let ReadFileResult.Failure(error):
println("Failed to open file. Error code: \(error.code)")
}
Adding Generics
Now that we have a Swift function for reading files, the obvious next challenge is to define a function for writing files. As a first approximation, we
might write the following:
func writeFile(contents: String,
path: String, encoding: Encoding) -> Bool {
return contents.writeToFile(path,
atomically: false,
1
encoding: toNSStringEncoding(encoding),
error: nil)
}
We can certainly write a new version of the writeFile function using this
enumeration but introducing a new enumeration for each possible function seems like overkill. Besides, the WriteFileResult and ReadFileResult
have an awful lot in common. The only difference between the two enumerations is the value associated with Success. We would like to define a
new enumeration that is generic in the result associated with Success:
enum Result<T> {
case Success(T)
case Failure(NSError)
}
class Box<T> {
let unbox: T
init(_ value: T) { self.unbox = value }
}
enum Result<T> {
case Success(Box<T>)
case Failure(NSError)
}
The Box class does not serve any particular purpose, except to hide the
associated generic value T in the Success member.
Now we can use the same result type for both readFile and writeFile.
Their new type signatures would become:
func readFile(path: String, encoding: Encoding) -> Result<String>
func writeFile(contents: String,
path: String, encoding: Encoding) -> Result<()>
Optionals Revisited
Under the hood, Swifts built-in optional type is very similar to the Result
type that weve defined here. The following snippet is taken directly from
the Swift standard library:
enum Optional<T> {
case None
case Some(T)
// ...
}
The optional type just provides some syntactic sugar, such as the postfix
? notation and optional unwrapping mechanism, to make it easier to use.
There is, however, no reason that you couldnt define it yourself.
In fact, we can even define some of the library functions for manipulating optionals on our own Result type. For example, our Result type also
supports a map operation:
func map<T, U>(f: T -> U, result: Result<T>) -> Result<U> {
switch result {
case let Result.Success(box):
return Result.Success(Box(f(box.unbox)))
case let Result.Failure(error):
return Result.Failure(error)
}
}
Similarly, we can redefine the ?? operator to work on our Result type. Note
that, instead of taking an autoclosure argument, we expect a function that
handles the NSError to produce the desired value of type T:
func ??<T>(result: Result<T>, handleError: NSError -> T) -> T {
switch result {
case let Result.Success(box):
return box.unbox
case let Result.Failure(error):
return handleError(error)
}
}
case InLeft(Box<T>)
case InRight(Box<U>)
}
For any types T and U, the enumeration Add<T, U> consists of either a
(boxed) value of type T, or a (boxed) value of type U. As its name suggests,
the Add enumeration adds together the members from the types T and U:
if T has three members and U has seven, Add<T, U> will have ten possible
members.
In arithmetic, zero is the unit of addition, i.e. x + 0 is the same as using
just x for any number x. Can we find an enumeration that behaves like
zero? Interestingly, Swift allows us to define the following enumeration:
enum Zero { }
Just as Zero was the unit of addition, the void type, (), is the unit of Times:
typealias One = ()
It is easy to check that many familiar laws from arithmetic are still valid
when read as isomorphisms between types:
Times<One, T> is isomorphic to T
Chapter 9
In the previous chapter, we saw how to use enumerations to define specific types tailored to the application you are developing. In this chapter,
we will define recursive enumerations and show how these can be used to
define data structures that are both efficient and persistent.
As a first attempt, we may use arrays to represent sets. These four operations are almost trivial to implement:
func emptySet<T>() -> Array<T> {
return []
}
func isEmptySet<T>(set: [T]) -> Bool {
return set.isEmpty
}
func setInsert<T>(x: T, set:[T]) -> [T] {
return [x] + set
}
func setContains<T: Equatable>(x: T, set: [T]) -> Bool {
return contains(set, x)
}
While simple, the drawback of this implementation is that many of the operations perform linearly in the size of the set. For large sets, this may
cause performance problems.
There are several possible ways to improve performance. For example,
we could ensure the array is sorted and use binary search to locate specific elements. Instead, we will define a binary search tree to represent
our sets. We can build a tree structure in the traditional C style, maintaining pointers to subtrees at every node. However, we can also define such
trees directly as an enumeration in Swift, using the same Box trick as in
the last chapter:
enum Tree<T> {
case Leaf
case Node(Box<Tree<T>>, T, Box<Tree<T>>)
}
Note: this currently compiles but hangs when run (Xcode 6.1, beta 2).
The leaf tree is empty; the five tree stores the value 5 at a node, but
both subtrees are empty. We can generalize this construction and write a
function that builds a tree with a single value:
func single<T>(x: T) -> Tree<T> {
return Tree.Node(Box(Tree.Leaf), x, Box(Tree.Leaf))
}
Just as we saw in the previous chapter, we can write functions that manipulate trees using switch statements. As the Tree enumeration itself
is recursive, it should come as no surprise that many functions that we
write over trees will also be recursive. For example, the following function
counts the number of elements stored in a tree:
func count<T>(tree: Tree<T>) -> Int {
switch tree {
case let Tree.Leaf:
return 0
case let Tree.Node(left, x, right):
return 1 + count(left.unbox) +
count(right.unbox)
}
}
In the base case for leaves, we can return 0 immediately. The case for
nodes is more interesting: we compute the number of elements stored in
both subtrees recursively. We then return their sum, and add 1 to account
for the value x stored at this node.
Similarly, we can write an elements function that calculates the array
of elements stored in a tree:
func elements<T>(tree: Tree<T>) -> [T] {
switch tree {
case let Tree.Leaf:
return []
case let Tree.Node(left, x, right):
return elements(left.unbox) + [x] +
elements(right.unbox)
}
}
Now lets return to our original goal, which is writing an efficient set library
using trees. We have obvious choices for the isEmptySet and emptySet functions:
func emptySet<T>() -> Tree<T> {
return Tree.Leaf
}
func isEmptySet<T>(tree: Tree<T>) -> Bool {
switch tree {
case let Tree.Leaf:
return true
case let Tree.Node(_, _, _):
return false
}
}
Note that in the Node case for the isEmptySet function, we do not need to
refer to the subtrees or the value stored at the node, but can immediately
return false. Correspondingly, we can put wildcard patterns for the three
values associated with a Node, indicating they are not used in this case
branch.
If we try to write naive versions of setInsert and setContains, however,
it seems that we have not gained much. If we restrict ourselves to binary
search trees, however, we can perform much better. A (non-empty) tree
is said to be a binary search tree if all of the following conditions are met:
all the values stored in the left subtree are less than the value
stored at the root
all the values stored in the right subtree are greater than the value
stored at the root
both the left and right subtrees are binary search trees
We can write an (inefficient) check to ascertain if a Tree is a binary search
tree or not:
func isBST<T: Comparable>(tree: Tree<T>) -> Bool {
switch tree {
case Tree.Leaf:
return true
case let Tree.Node(left, x, right):
let leftElements = elements(left.unbox)
let rightElements = elements(right.unbox)
return all(leftElements) { y in y < x }
&& all(rightElements) { y in y > x }
&& isBST(left.unbox)
&& isBST(right.unbox)
}
}
The all function checks if a property holds for all elements in an array. It
is defined in the appendix of this book.
The crucial property of binary search trees is that they admit an efficient lookup operation, akin to binary search in an array. As we traverse
Unfortunately, this function is not very efficient. For large histories and
long prefixes, it may be too slow. Once again, we could improve performance by keeping the history sorted and using some kind of binary search
on the history array. Instead, we will explore a different solution, using a
custom data structure tailored for this kind of query.
Tries, also known as digital search trees, are a particular kind of ordered tree. Typically, tries are used to look up a string, which consists of a
list of characters. Instead of storing strings in a binary search tree, it can
be more efficient to store them in a structure that repeatedly branches
over the strings constituent characters.
Previously, the binary Tree type had two subtrees at every node. Tries,
on the other hand, do not have a fixed number of subtrees at every node,
but instead (potentially) have subtrees for every character. For example,
we could visualize a trie storing the string cat, car, cart, and dog
as follows:
To determine if the string care is in the trie, we follow the path from
the root, along the edges labeled c, a, and r. As the node labeled r
does not have a child labeled with e, the string care is not in this trie.
d
o
a
r
Figure 9.1:
The string cat is in the trie, as we can follow a path from the root along
edges labeled c, a, and t.
How can we represent such tries in Swift? As a first attempt, we write
an enumeration storing a dictionary, mapping characters to subtries at
every node:
enum Trie {
case Node([Character: Trie])
}
There are two improvements we would like to make to this definition. First
of all, we need to add some additional information to the node. From the
example trie above, you can see that by adding cart to the trie, all the
prefixes of cart namely c, ca, and car also appear in the trie.
As we may want to distinguish between prefixes that are or are not in the
trie, we will add an additional boolean to every node. This boolean indicates whether or not the current string is in the trie. Finally, we can define
a generic trie that is no longer restricted to only storing characters. Doing
so yields the following definition of tries:
enum Trie<T: Hashable> {
In the text that follows, we will sometimes refer to the keys of type [T] as
strings, and values of type T as characters. This is not very precise as
T can be instantiated with a type different than characters, and a string is
not the same as [Character] but we hope it does appeal to the intuition
of tries storing a collection of strings.
Before defining our autocomplete function on tries, we will write a few
simple definitions to warm up. For example, the empty trie consists of a
node with an empty dictionary:
func empty<T: Hashable>() -> Trie<T> {
return Trie.Make(false, [T: Trie<T>]())
}
If we had chosen to set the boolean stored in the empty trie to true rather
than false, the empty string would be a member of the empty trie which
is probably not the behavior that we want.
Next, we define a function to flatten a trie into an array containing all
its elements:
func elements<T: Hashable>(trie: Trie<T>) -> [[T]] {
switch trie {
case let Trie.Make(isElem, rest):
var result: [[T]] = isElem ? [[]] : []
for (key, value) in rest {
result += elements(value).map {xs in
[key] + xs
}
}
return result
}
}
key; if it is not, the result variable is initialized to the empty array. Next, it
traverses the dictionary, computing the elements of the subtries this is
done by the call elements(value). Finally, the character associated with
every subtrie is added to the front of the elements of that subtrie this
is taken care of by the map function.
Next, we would like to define lookup and insertion functions. Before
we do so, however, we will need a few auxiliary functions. We have represented keys as an array. While our tries are defined as (recursive) enumerations, arrays are not. Yet it can still be useful to traverse an array
recursively. To make this a bit easier, we define the following extension
on arrays:
extension Array {
var decompose : (head: T, tail: [T])? {
return (count > 0) ? (self[0], Array(self[1..<count])) : nil
}
}
}
}
else { return isElem }
}
return false
}
return nil
}
The only difference with the lookup function is that we no longer return
the isElem boolean, but instead return the whole subtrie, containing all
the elements with the argument prefix.
Finally, we can redefine our autocomplete function to use the more efficient tries data structure:
func autocomplete<T: Hashable>(key: [T],
trie: Trie<T>) -> [[T]] {
if let prefixTrie = withPrefix(key, trie) {
return elements(prefixTrie)
} else {
return []
}
}
To compute all the strings in a trie with a given prefix, we simply call the
withPrefix function and extract the elements from the resulting trie, if it
exists. If there is no subtrie with the given prefix, we simply return the
empty array.
To complete the library, you may still want to write insertion and deletion functions yourself.
Discussion
These are but two examples of writing efficient, immutable data structures using enumerations. There are many others in Chris Okasakis Purely
Functional Data Structures (1999), which is a standard reference on the
subject. Interested readers may also want to read Ralph Hinze and Ross
Patersons work on finger trees (2006), which are general-purpose purely
functional data structures with numerous applications. Finally, StackOverflow has a fantastic list of more recent research in this area.
Chapter 10
Diagrams
This is nice and short, but it is a bit difficult to maintain. For example, what
if we wanted to add an extra circle like in Figure 10.2?
We would need to add the code for drawing a rectangle, but also update
the drawing code to move some of the other objects to the right. In Core
Graphics, we always describe how to draw things. In this chapter, well
build a library for diagrams that allows us to express what we want draw.
For example, the first diagram can be expressed like this:
let blueSquare = square(side: 1).fill(NSColor.blueColor())
let redSquare = square(side: 2).fill(NSColor.redColor())
let greenCircle = circle(radius: 1).fill(NSColor.greenColor())
let example1 = blueSquare ||| redSquare ||| greenCircle
Adding the second circle is as simple as changing the last line of code:
let cyanCircle = circle(radius: 1).fill(NSColor.cyanColor())
let example2 = blueSquare ||| cyanCircle |||
redSquare ||| greenCircle
The code above first describes a blue square with a relative size of 1. The
red square is twice as big (it has a relative size of 2). We compose the
diagram by putting the squares and the circle next to each other with the
||| operator. Changing this diagram is very simple, and theres no need
to worry about calculating frames or moving things around. The examples
describe what should be drawn, not how it should be drawn.
One of the techniques well use in this chapter is building up an intermediate structure of the diagram. Instead of executing the drawing commands immediately, we build up a data structure that describes the diagram. This is a very powerful technique, as it allows us to inspect the data
structure, modify it, and convert it into different formats.
As a more complex example of a diagram generated by the same library,
Figure 10.3 shows a bar graph.
We can write a barGraph function that takes a list of names (the keys)
and values (the relative heights of the bars). For each value in the dictionary, we draw a suitably sized rectangle. We then horizontally concatenate these rectangles with the hcat function. Finally, we put the bars and
the text below each other using the --- operator:
Moscow
Shanghai Istanbul
Berlin
New York
Then, we have cases for diagrams that are beside each other (horizontally)
or below each other (vertically). Note how a Beside diagram is defined recursively it consists of two diagrams next to each other:
case Beside(Diagram, Diagram)
case Below(Diagram, Diagram)
To style diagrams, well add a case for attributed diagrams. This allows us
to set the fill color (for example, for ellipses and rectangles). Well define
the Attribute type later:
case Attributed(Attribute, Diagram)
The last case is for alignment. Suppose we have a small and a large rectangle that are next to each other. By default, the small rectangle gets
centered vertically, as seen in Figure 10.4.
For example, Figure 10.5 shows a diagram thats top aligned. It is drawn
using the following code:
Unfortunately, in the current version of Swift, recursive data types are not
allowed. So instead of having a Diagram case that contains other Diagrams,
we have to wrap each recursive Diagram with Box (defined in Chapter 8)
enum Diagram {
case Prim(CGSize, Primitive)
Before we start drawing, we will first define one more function. The fit
function takes an alignment vector (which we used in the Align case of a
diagram), an input size (i.e. the size of a diagram), and a rectangle that
we want to fit the input size into. The input size is defined relatively to
the other elements in our diagram. We scale it up and maintain its aspect
ratio:
func fit(alignment: Vector2D,
inputSize: CGSize, rect: CGRect) -> CGRect {
let div = rect.size / inputSize
let scale = min(div.width, div.height)
let size = scale * inputSize
let space = alignment.size * (size - rect.size)
return CGRect(origin: rect.origin - space.point, size: size)
}
> (50.0,0.0,100.0,100.0)
> (0.0,0.0,100.0,100.0)
Now that we can represent diagrams and calculate their sizes, were ready
to draw them. We use pattern matching to make it easy to know what to
draw. The draw method takes a few parameters: the context to draw in, the
bounds to draw in, and the actual diagram. Given the bounds, the diagram
will try to fit itself into the bounds using the fit function defined before.
For example, when we draw an ellipse, we center it and make it fill the
available bounds:
func draw(context: CGContextRef, bounds: CGRect, diagram: Diagram) {
switch diagram {
case .Prim(let size, .Ellipsis):
let frame = fit(Vector2D(x: 0.5, y: 0.5), size, bounds)
CGContextFillEllipseInRect(context, frame)
For rectangles, this is almost the same, except that we call a different
Core Graphics function. You might note that the frame calculation is the
same as for ellipses. It would be possible to pull this out and have a nested
switch statement, but we think this is more readable when presenting in
book form:
case .Prim(let size, .Rectangle):
let frame = fit(Vector2D(x: 0.5, y: 0.5), size, bounds)
CGContextFillRect(context, frame)
In the current version of our library, all text is set in the system font with a
fixed size. Its very possible to make this an attribute, or change the Text
primitive to make this configurable. In its current form though, drawing
text works like this:
case .Prim(let size, .Text(let text)):
let frame = fit(Vector2D(x: 0.5, y: 0.5), size, bounds)
The only attribute we support is fill color. Its very easy to add support for
extra attributes, but we left that out for brevity. To draw a diagram with a
FillColor attribute, we save the current graphics state, set the fill color,
draw the diagram, and finally, restore the graphics state:
case .Attributed(.FillColor(let color), let d):
CGContextSaveGState(context)
color.set()
draw(context, bounds, d.unbox)
CGContextRestoreGState(context)
To draw two diagrams next to each other, we first need to find their respective frames. We create a function, splitHorizontal, that splits a CGRect according to a ratio (in this case, the relative size of the left diagram). Then
we draw both diagrams with their frames:
case .Beside(let left, let right):
let l = left.unbox
let r = right.unbox
let (lFrame, rFrame) = splitHorizontal(
bounds, l.size/diagram.size)
draw(context, lFrame, l)
draw(context, rFrame, r)
The case for Below is exactly the same, except that we split the CGRect vertically instead of horizontally. This code was written to run on the Mac, and
therefore the order is bottom and top (unlike UIKit, the Cocoa coordinate
system has the origin at the bottom left):
case .Below(let top, let bottom):
let t = top.unbox
let b = bottom.unbox
let (lFrame, rFrame) = splitVertical(
bounds, b.size/diagram.size)
draw(context, lFrame, b)
draw(context, rFrame, t)
Our last case is aligning diagrams. Here, we can reuse the fit function that
we defined earlier to calculate new bounds that fit the diagram exactly:
case .Align(let vec, let d):
let diagram = d.unbox
let frame = fit(vec, diagram.size, bounds)
draw(context, frame, diagram)
}
}
Weve now defined the core of our library. All the other things can be built
on top of these primitives.
Now that we have an NSView, its also very simple to make a PDF out
of our diagrams. We calculate the size and just use NSViews method,
dataWithPDFInsideRect, to get the PDF data. This is a nice example of
taking existing object-oriented code, and wrapping it in a functional
layer:
func pdf(diagram: Diagram, width: CGFloat) -> NSData {
let unitSize = diagram.size
let height = width * (unitSize.height/unitSize.width)
let v: Draw = Draw(frame: NSMakeRect(0, 0, width, height),
diagram: diagram)
return v.dataWithPDFInsideRect(v.bounds)
}
Extra Combinators
To make the construction of diagrams easier, its nice to add some extra
functions (also called combinators). This is a common pattern in functional libraries: have a small set of core data types and functions, and
then build convenience functions on top of them. For example, for rectangles, circles, text, and squares, we can define convenience functions:
func rect(#width: CGFloat, #height: CGFloat) -> Diagram {
return Diagram.Prim(CGSizeMake(width, height), .Rectangle)
}
func circle(#radius: CGFloat) -> Diagram {
Also, it turns out that its very convenient to have operators for combining diagrams horizontally and vertically, making the code more readable.
They are just wrappers around Beside and Below:
infix operator ||| { associativity left }
func ||| (l: Diagram, r: Diagram) -> Diagram {
return Diagram.Beside(Box(l), Box(r))
}
infix operator --- { associativity left }
func --- (l: Diagram, r: Diagram) -> Diagram {
return Diagram.Below(Box(l), Box(r))
}
We can also extend the Diagram type and add methods for filling and alignment. We also might have defined these methods as top-level functions
instead. This is a matter of style; one is not more powerful than the other:
extension Diagram {
func fill(color: NSColor) -> Diagram {
return Diagram.Attributed(Attribute.FillColor(color), Box(self))
}
Finally, we can define an empty diagram and a way to horizontally concatenate a list of diagrams. We can just use the arrays reduce function to
do this:
let empty: Diagram = rect(width: 0, height: 0)
func hcat(diagrams: [Diagram]) -> Diagram {
return diagrams.reduce(empty, combine: |||)
}
Discussion
The code in this chapter is inspired by the Diagrams library for Haskell
(Yorgey 2012). Although we can draw simple diagrams, there are many
possible improvements and extensions to the library we have presented
here. Some things are still missing but can be added easily. For example,
its straightforward to add more attributes and styling options. A bit more
complicated would be adding transformations (such as rotation), but this
is certainly still possible.
When we compare the library that weve built in this chapter to the library in Chapter 2, we can see many similarities. Both take a problem domain (regions and diagrams) and create a small library of functions to describe this domain. Both libraries provide an interface through functions
that are highly composable. Both of these little libraries define a domainspecific language (or DSL) embedded in Swift. A DSL is a small programming language, tailored to solve a particular problem. You are probably
already familiar with lots of DSLs, such as regular expressions, SQL, or
HTML each of these languages is not a general-purpose programming
language in which to write any application, but instead is more restricted
to solve a particular kind of problem. Regular expressions are used for describing patterns or lexers, SQL is used for querying a database, and HTML
is used for describing the content of a webpage.
However, there is an important difference between the two DSLs:
in the Thinking Functionally chapter, we created functions that return
a bool for each position. To draw the diagrams, we built up an intermediate structure, the Diagram enum. A shallow embedding of a DSL
in a general-purpose programming language like Swift does not create
any intermediate data structures. A deep embedding, on the other
hand, explicitly creates an intermediate data structure, like the Diagram
enumeration described in this chapter. The term embedding refers to
how the DSL for regions or diagrams are embedded into Swift. Both
have their advantages. A shallow embedding can be easier to write, there
is less overhead during execution, and it can be easier to extend with
new functions. However, when using a deep embedding, we have the
advantage that we can analyze an entire structure, transform it, or assign
different meanings to the intermediate data structure.
If we would rewrite the DSL from Chapter 2 to use deep embedding instead, we would need to define an enumeration representing the different
functions from the library. There would be members for our primitive regions, like circles or squares, and members for composite regions, such
as those formed by intersection or union. We could then analyze and compute with these regions in different ways: generating images, checking
whether a region is primitive or not, determining whether or not a given
point is in the region, or performing an arbitrary computation over this intermediate data structure. Rewriting the diagrams library to a shallow
embedding would be complicated. The intermediate data structure can
be inspected, modified, and transformed. To define a shallow embedding,
we would need to call Core Graphics directly for every operation that we
wish to support in our DSL. It is much more difficult to compose drawing
calls than it is to first create an intermediate structure and only render it
once the diagram has been completely assembled.
Chapter 11
In this chapter, well look at generators and sequences. These form the
machinery underlying Swifts for loops, and will be the basis of the parsing
library that we will present in the following chapters.
Generators
In Objective-C and Swift, we almost always use the Array datatype to represent a list of items. It is both simple and fast. There are situations, however, where arrays are not suitable. For example, you might not want to
calculate all the elements of an array, because there is an infinite amount,
or you dont expect to use them all. In such situations, you may want to
use a generator instead.
We will try to provide some motivation for generators, using familiar examples from array computations. Swifts for loops can be used to iterate
over array elements:
for x in xs {
// do something with x
}
In such a for loop, the array is traversed from beginning to end. There may
be examples, however, where you want to traverse arrays in a different
order. This is where generators may be useful.
Conceptually, a generator is a process that generates new array elements on request. A generator is any type that adheres to the following
protocol:
protocol GeneratorType {
typealias Element
func next() -> Element?
}
For the sake of convenience, we provide two initializers: one that counts
down from an initial number start, and one that is passed an array and
initializes the element to the arrays last valid index.
We can use this CountdownGenerator to traverse an array backward:
Although it may seem like overkill on such simple examples, the generator
encapsulates the computation of array indices. If we want to compute
the indices in a different order, we only need to update the generator, and
never the code that uses it.
Generators need not produce a nil value at some point. For example,
we can define a generator that produces an infinite series of powers of
two (until NSDecimalNumber overflows, which is only with extremely large
values):
class PowerGenerator: GeneratorType {
typealias Element = NSDecimalNumber
var power: NSDecimalNumber = NSDecimalNumber(int: 1)
let two = NSDecimalNumber(int: 2)
func next() -> Element? {
power = power.decimalNumberByMultiplyingBy(two)
return power
}
}
We may also want to use the PowerGenerator for something entirely different. Suppose we want to search through the powers of two, looking for
some interesting value. The findPower function takes a predicate of type
NSDecimalNumber -> Bool as argument, and returns the smallest power of
two that satisfies this predicate:
func findPower(predicate: NSDecimalNumber -> Bool)
-> NSDecimalNumber {
let g = PowerGenerator()
while let x = g.next() {
if predicate(x) {
return x
}
}
return 0;
}
We can use the findPower function to compute the smallest power of two
larger than 1,000:
findPower { $0.integerValue > 1000 }
> 1024
The generators we have seen so far all produce numerical elements, but
this need not be the case. We can just as well write generators that produce some other value. For example, the following generator produces a
list of strings, corresponding to the lines of a file:
class FileLinesGenerator: GeneratorType {
typealias Element = String
var lines: [String]
init(filename: String) {
if let contents = String(contentsOfFile: filename,
encoding: NSUTF8StringEncoding,
error: nil) {
let newLine = NSCharacterSet.newlineCharacterSet()
lines = contents
.componentsSeparatedByCharactersInSet(newLine)
} else {
lines = []
}
}
func next() -> Element? {
if let nextLine = lines.first {
lines.removeAtIndex(0)
return nextLine
} else {
return nil
}
}
}
if predicate(x) {
return x
}
}
return nil
}
The find function is generic over any possible generator. The most interesting thing about it is its type signature. The find function takes two arguments: a generator and a predicate. The generator may be modified by
the find function, resulting from the calls to next, hence we need to add
the var attribute in the type declaration. The predicate should be a function mapping generated elements to Bool. We can refer to the generators
associated type as G.Element, in the type signature of find. Finally, note
that we may not succeed in finding a value that satisfies the predicate. For
that reason, find returns an optional value, returning nil when the generator is exhausted.
It is also possible to combine generators on top of one another. For
example, you may want to limit the number of items generated, buffer the
generated values, or encrypt the data generated. Here is one simple example of a generator transformer that produces the first limit values from its
argument generator:
class LimitGenerator<G: GeneratorType>: GeneratorType {
typealias Element = G.Element
var limit = 0
var generator: G
init(limit: Int, generator: G) {
self.limit = limit
self.generator = generator
}
func next() -> Element? {
if limit >= 0 {
limit-return generator.next()
}
else {
return nil
}
}
}
The resulting generator simply reads off new elements from its first argument generator; once this is exhausted, it produces elements from its
second generator. Once both generators have returned nil, the composite
generator also returns nil.
Sequences
Generators form the basis of another Swift protocol, sequences. Generators provide a one-shot mechanism for repeatedly computing a next element. There is no way to rewind or replay the elements generated. The
only thing we can do is create a fresh generator and use that instead. The
SequenceType protocol provides just the right interface for doing that:
protocol SequenceType {
typealias Generator: GeneratorType
func generate() -> Generator
}
> Index 2 is C
> Index 1 is B
> Index 0 is A
> ()
In contrast to the previous example that just used the generator, the same
sequence can be traversed a second time we would simply call generate
to produce a new generator.
Swift has special syntax for working with sequences. Instead of creating the generator associated with a sequence yourself, you can write a
for-in loop. For example, we can also write the previous code snippet as:
> Index 2 is C
> Index 1 is B
> Index 0 is A
> ()
Under the hood, Swift then uses the generate method to produce a generator and repeatedly call its next function until it produces nil.
The obvious drawback of our CountdownGenerator is that it produces
numbers, while we may be interested in the elements associated with an
array. Fortunately, there are standard map and filter functions that manipulate sequences rather than arrays:
func filter<S: SequenceType>(source: S,
includeElement: S.Generator.Element -> Bool)
-> [S.Generator.Element]
func map<S: SequenceType, T>(source: S,
transform: S.Generator.Element -> T)
-> [T]
To produce the elements of an array in reverse order, we can map over our
ReverseSequence:
let reverseElements = map(ReverseSequence(array: xs)) { i in xs[i] }
for x in reverseElements {
println("Element is \(x)")
}
> Element is C
> Element is B
> Element is A
> ()
Similarly, we may of course want to filter out certain elements from a sequence.
It is worth pointing out that these map and filter functions do not return new sequences, but instead traverse the sequence to produce an array. Mathematicians may therefore object to calling such operations maps,
as they fail to leave the underlying structure (a sequence) intact. There are
separate versions of map and filter that do produce sequences. These are
defined as extensions of the LazySequence class. A LazySequence is a simple wrapper around regular sequences:
func lazy<S: SequenceType>(s: S) -> LazySequence<S>
If you need to map or filter sequences that may produce either infinite results, or many results that you may not be interested in, be sure to use
a LazySequence rather than a Sequence. Failing to do so could cause your
program to diverge or take much longer than you might expect.
We used the Smaller protocol to try and shrink counterexamples that our
testing uncovered. The smaller function is repeatedly called to generate
a smaller value; if this value still fails the test, it is considered a better
counterexample than the original one. The Smaller instance we defined
for arrays simply tried to repeatedly strip off the first element:
extension Array: Smaller {
func smaller() -> [T]? {
if (!self.isEmpty) {
return Array(dropFirst(self))
}
return nil
}
}
While this will certainly help shrink counterexamples in some cases, there
are many different ways to shrink an array. Computing all possible subarrays is an expensive operation. For an array of length n, there are 2^n
possible subarrays that may or may not be interesting counterexamples
generating and testing them is not a good idea.
Instead, we will show how to use a generator to produce a series of
smaller values. We can then adapt our QuickCheck library to use the following protocol:
protocol Smaller {
func smaller() -> GeneratorOf<Self>
}
return result
}
return nil
}
}
Unfortunately, this call does not produce the desired result it defines a
GeneratorOf<[Int]>, whereas we would like to see an array of arrays. Fortunately, there is an Array initializer that takes a Sequence as argument.
Using that initializer, we can test our generator as follows:
Array(removeAnElement([1, 2, 3]))
Of course, this may not be a very good example: a function like sum is easy
to write using reduce. However, this is not true for all functions on arrays. For example, consider the problem of inserting a new element into
a sorted array. Writing this using reduce is not at all easy; writing it as a
recursive function is fairly straightforward:
func insert(x: Int, xs: [Int]) -> [Int] {
if let (head, tail) = xs.decompose {
return x <= head ? [x] + xs
: [head] + insert(x, tail)
} else {
return [x]
}
}
Given an optional element, it generates the sequence with just that element (provided it is non-nil):
let three: [Int] = Array(GeneratorOfOne(3))
let empty: [Int] = Array(GeneratorOfOne(nil))
For the sake of convenience, we will define our own little wrapper function
around GeneratorOfOne:
func one<X>(x: X?) -> GeneratorOf<X> {
return GeneratorOf(GeneratorOfOne(x))
}
Now we finally return to our original problem: redefining the smaller function on arrays. If we try to formulate a recursive pseudocode definition
of what our original removeElement function computed, we might arrive at
something along the following lines:
If the array is empty, return nil
If the array can be split into a head and tail, we can recursively compute the remaining subarrays as follows:
tail of the array is a subarray
if we prepend head to all the subarrays of the tail, we can compute the subarrays of the original array
We can translate this algorithm directly into Swift with the functions we
have defined:
func smaller1<T>(array: [T]) -> GeneratorOf<[T]> {
if let (head, tail) = array.decompose {
let gen1: GeneratorOf<[T]> = one(tail)
let gen2: GeneratorOf<[T]> = map(smaller1(tail)) {
smallerTail in
[head] + smallerTail
}
return gen1 + gen2
} else {
return one(nil)
}
}
Were now ready to test our functional variant, and we can verify that its
the same result as removeAnElement:
Array(smaller1([1, 2, 3]))
Here, there is one thing we should point out. In this definition of smaller,
we are using our own version of map:
func map<A, B>(var g: GeneratorOf<A>, f: A -> B) -> GeneratorOf<B> {
return GeneratorOf {
g.next().map(f)
}
}
You may recall that the map and filter methods from the standard library
return a LazySequence. To avoid the overhead of wrapping and unwrapping
these lazy sequences, we have chosen to manipulate the GeneratorOf directly.
There is one last improvement worth making: there is one more way
to try and reduce the counterexamples that QuickCheck finds. Instead of
just removing elements, we may also want to try and shrink the elements
themselves. To do that, we need to add a condition that T conforms to the
smaller protocol:
func smaller<T: Smaller>(ls: [T]) -> GeneratorOf<[T]> {
if let (head, tail) = ls.decompose {
let gen1: GeneratorOf<[T]> = one(tail)
let gen2: GeneratorOf<[T]> = map(smaller(tail), { xs in
[head] + xs
})
let gen3: GeneratorOf<[T]> = map(head.smaller(), { x in
[x] + tail
})
return gen1 + gen2 + gen3
} else {
return one(nil)
}
}
> [[2, 3], [1, 3], [1, 2], [1, 2, 2], [1, 1, 3], [0, 2, 3]]
This definition calls the generate method of the two argument sequences,
concatenates these, and assigns the resulting generator to the sequence.
Unfortunately, it does not quite work as expected. Consider the following
example:
let s = SequenceOf([1, 2, 3]) + SequenceOf([4, 5, 6])
print("First pass: ")
for x in s {
print(x)
}
println("\nSecond pass:")
for x in s {
print(x)
}
The second for loop is not producing any output what went wrong? The
problem is in the definition of concatenation on sequences. We assemble
the desired generator, l.generate() + r.generate(). This generator produces all the desired elements in the first loop in the example above. Once
it has been exhausted, however, traversing the compound sequence a second time will not produce a fresh generator, but instead use the generator
that has already been exhausted.
Fortunately, this problem is easy to fix. We need to ensure that the
result of our concatenation operation can produce new generators. To do
so, we pass a function that produces generators, rather than passing a
fixed generator to the SequenceOf initializer:
func +<A>(l: SequenceOf<A>, r: SequenceOf<A>) -> SequenceOf<A> {
return SequenceOf { l.generate() + r.generate() }
}
Now, we can iterate over the same sequence multiple times. When writing
your own methods that combine sequences, it is important to ensure that
every call to generate() produces a fresh generator that is oblivious to any
previous traversals.
Thus far, we can concatenate two sequences. What about flattening a
sequence of sequences? Before we deal with sequences, lets try writing
a join operation that, given a GeneratorOf<GeneratorOf<A>>, produces a
GeneratorOf<A>:
struct JoinedGenerator<A>: GeneratorType {
typealias Element = A
var generator: GeneratorOf<GeneratorOf<A>>
var current: GeneratorOf<A>?
init(_ g: GeneratorOf<GeneratorOf<A>>) {
generator = g
current = generator.next()
}
mutating func next() -> A? {
if var c = current {
if let x = c.next() {
return x
} else {
current = generator.next()
return next()
}
}
return nil
}
}
})
}
}
Chapter 12
Parser Combinators
Parsers are very useful tools: they take a list of tokens (usually, a list of
characters) and transform it into a structure. Often, parsers are generated
using an external tool, such as Bison or YACC. Instead of using an external
tool, well build a parser library in this chapter, which we can use later for
building our own parser. Functional languages are very well suited for this
task.
There are several approaches to writing a parsing library. Here well
build a parser combinator library. A parser combinator is a higher-order
function that takes several parsers as input and returns a new parser as its
output. The library well build is an almost direct port of a Haskell library
(2009), with a few modifications.
We will start with defining a couple of core combinators. On top of that,
we will build some extra convenience functions, and finally, we will show
an example that parses arithmetic expressions, such as 1+3*3, and calculates the result.
The Core
In this library, well make heavy use of sequences and slices.
Wed rather use a type alias to define our parser type, but type aliases
dont support generic types. Therefore, we have to live with the indirection
of using a struct in this case.
Lets start with a very simple parser that parses the single character
"a". To do this, we write a function that returns the "a" character parser:
func parseA() -> Parser<Character, Character>
Note that it returns a parser with the token type Character, as well as the
result type Character. The results of this parser will be tuples of an "a"
character and the remainder of characters. It works like this: it splits the
input stream into head (the first character) and tail (all remaining characters), and returns a single result if the first character is an "a". If the first
character isnt an "a", the parser fails by returning none(), which is simply
an empty sequence:
func parseA() -> Parser<Character, Character> {
let a: Character = "a"
return Parser { x in
if let (head, tail) = x.decompose {
if head == a {
return one((a, tail))
}
}
return none()
}
}
We can test it using the testParser function. This runs the parser given by
the first argument over the input string that is given by the second argument. The parser will generate a sequence of possible results, which get
printed out by the testParser function. Usually, we are only interested in
the very first result:
testParser(parseA(), "abcd")
If we run the parser on a string that doesnt contain an "a" at the start, we
get a failure:
testParser(parseA(), "test")
We can abstract this method one final time, making it generic over any
kind of token. Instead of checking if the token is equal, we pass in a function with type Token -> Bool, and if the function returns true for the first
character in the stream, we return it:
func satisfy<Token>(condition: Token -> Bool)
-> Parser<Token, Token> {
return Parser { x in
if let (head, tail) = x.decompose {
if condition(head) {
return one((head, tail))
}
}
return none()
}
}
Now we can define a function token that works like parseCharacter, the
only difference being that it can be used with any type that conforms to
Equatable:
func token<Token: Equatable>(t: Token) -> Parser<Token, Token> {
return satisfy { $0 == t }
}
Choice
Parsing a single symbol isnt very useful, unless we add functions to combine two parsers. The first function that we will introduce is the choice operator, and it can parse using either the left operand or the right operand.
It is implemented in a simple way: given an input string, it runs the left
operands parser, which yields a sequence of possible results. Then it
runs the right operand, which also yields a sequence of possible results,
and it concatenates the two sequences. Note that the left and the right
sequences might both be empty, or contain a lot of elements. Because
they are calculated lazily, it doesnt really matter:
infix operator <|> { associativity right precedence 130 }
func <|> <Token, A>(l: Parser<Token, A>,
r: Parser<Token, A>) -> Parser<Token, A> {
return Parser { input in
l.p(input) + r.p(input)
}
}
To test our new operator, we build a parser that parses either an "a" or a
"b":
let a: Character = "a"
let b: Character = "b"
Sequence
To combine two parsers that occur after each other, well start with a more
naive approach and expand that later to something more convenient and
powerful. First we write a sequence function:
func sequence<Token, A, B>(l: Parser<Token, A>,
r: Parser<Token, B>)
-> Parser<Token, (A, B)>
The returned parser first uses the left parser to parse something of type
A. Lets say we wanted to parse the string "xyz" for an "x" immediately
followed by a "y." The left parser (the one looking for an "x") would then
generate the following sequence containing a single (result, remainder)
tuple:
[ ("x", "yz") ]
Applying the right parser to the remainder ("yz") of the left parsers tuple
yields another sequence with one tuple:
[ ("y", "z") ]
We then combine those tuples by grouping the "x" and "y" into a new tuple
(x, y):
[ (("x", "y"), "z") ]
Since we are doing these steps for each tuple in the returned sequence of
the left parser, we end up with a sequence of sequences:
[ [ (("x", "y"), "z") ] ]
}
}
Note that the above parser only succeeds if both l and r succeed. If they
dont, no tokens are consumed.
We can test our parser by trying to parse a sequence of an "x" followed
by a "y":
let x: Character = "x"
let y: Character = "y"
Refining Sequences
The sequence function we wrote above is a first approach to combine multiple parsers that are applied after each other. Imagine we wanted to parse
the same string "xyz" as above, but this time we want to parse "x", followed by "y", followed by "z". We could try to use the sequence function in
a nested way to combine three parsers:
let z: Character = "z"
The problem of this approach is that it yields a nested tuple (("x", "y"), "z")
instead of a flat one ("x", "y", "z"). To rectify this, we could write a
sequence3 function that combines three parsers instead of just two:
This returns the expected result, but the approach is way too inflexible
and doesnt scale. It turns out there is a much more convenient way to
combine multiple parsers in sequence.
As a first step, we create a parser that consumes no tokens at all, and
returns a function A -> B. This function takes on the job of transforming
the result of one or more other parsers in the way we want it to. A very
simple example of such a parser could be:
func integerParser<Token>() -> Parser<Token, Character -> Int> {
return Parser { input in
return one(({ x in String(x).toInt()! }, input))
}
}
This parser doesnt consume any tokens and returns a function that takes
a character and turns it into an integer. Lets use the extremely simple input stream "3" as example. Applying the integerParser to this input yields
the following sequence:
[ (A -> B, "3") ]
Applying another parser to parse the symbol "3" in the remainder (which
is equal to the original input since the integerParser didnt consume any
tokens) yields:
[ ("3", "") ]
Now we just have to create a function that combines these two parsers
and returns a new parser, so that the function yielded by integerParser
gets applied to the character "3" yielded by the symbol parser. This function looks very similar to the sequence function it calls flatMap on the
sequence returned by the first parser, and then maps over the sequence
returned by the second parser applied to the remainder.
The key difference is that the inner closure does not return the results
of both parsers in a tuple as sequence did, but it applies the function
yielded by the first parser to the result of the second parser:
func combinator<Token, A, B>(l: Parser<Token, A -> B>,
r: Parser<Token, A>)
-> Parser<Token, B> {
return Parser { input in
let leftResults = l.p(input)
return flatMap(leftResults) { f, leftRemainder in
let rightResults = r.p(leftRemainder)
return map(rightResults) { x, rightRemainder in
(f(x), rightRemainder)
}
}
}
}
Now weve laid the groundwork to build a really elegant parser combination mechanism.
The first thing well do is refactor our integerParser function into a
generic function, with one parameter that returns a parser that always
succeeds, consumes no tokens, and returns the parameter we passed into
the function as result:
func pure<Token, A>(value: A) -> Parser<Token, A> {
return Parser { one((value, $0)) }
}
With this in place, we can rewrite the previous example like this:
func toInteger(c: Character) -> Int {
return String(c).toInt()!
}
testParser(combinator(pure(toInteger), token(three)), "3")
testParser(combinator(combinator(pure(toInteger2), token(three)),
token(three)), "33")
Since nesting a lot of combinator calls within each other is not very readable, we define an operator for it:
infix operator <*> { associativity left precedence 150 }
func <*><Token, A, B>(l: Parser<Token, A -> B>,
r: Parser<Token, A>) -> Parser<Token, B> {
return Parser { input in
let leftResults = l.p(input)
return flatMap(leftResults) { f, leftRemainder in
let rightResults = r.p(leftRemainder)
return map(rightResults) { x, y in (f(x), y) }
}
}
}
Notice that we have defined the <*> operator to have left precedence. This
means that the operator will first be applied to the left two parsers, and
then to the result of this operation and the right parser. In other words,
this behavior is exactly the same as our nested combinator function calls
above.
Another example of how we can now use this operator is to create a
parser that combines several characters into a string:
let aOrB = token(a) <|> token(b)
Convenience Combinators
Using the above combinators, we can already parse a lot of interesting
languages. However, they can be a bit tedious to express. Luckily, there
are some extra functions we can define to make life easier. First we will
define a function to parse a character from an NSCharacterSet. This can be
used, for example, to create a parser that parses decimal digits:
func characterFromSet(set: NSCharacterSet)
-> Parser<Character, Character> {
return satisfy { return member(set, $0) }
}
let decimals = NSCharacterSet.decimalDigitCharacterSet()
let decimalDigit = characterFromSet(decimals)
The next convenience combinator we want to write is a zeroOrMore function, which executes a parser zero or more times:
func zeroOrMore<Token, A>(p: Parser<Token, A>) -> Parser<Token, [A]> {
return (pure(prepend) <*> p <*> zeroOrMore(p)) <|> pure([])
}
The prepend function combines a value of type A and an array [A] into a
new array.
However, if we try to use this function, we will get stuck in an infinite
loop. Thats because of the recursive call of zeroOrMore in the return statement.
Luckily, we can use auto-closures to defer the evaluation of the recursive call to zeroOrMore until it is really needed, and with that, break the
infinite recursion. To do that, we will first define a helper function, lazy.
It returns a parser that will only be executed once its actually needed, because we use the @autoclosure keyword for the function parameter:
func lazy<Token, A>(f: @autoclosure () -> Parser<Token, A>)
-> Parser<Token, A> {
return Parser { x in f().p(x) }
}
If we parse one or more digits, we get back an array of digits in the form of
Characters. To convert this into an integer, we can first convert the array
of Characters into a string, and then just call the built-in toInt() function
on it. Even though toInt might return nil, we know that it will succeed, so
we can force it with the ! operator:
let number = pure { characters in string(characters).toInt()! }
<*> oneOrMore(decimalDigit)
testParser(number, "205")
> Success, found 205, remainder: []
> Success, found 20, remainder: [5]
> Success, found 2, remainder: [0, 5]
If we look at the code weve written so far, we see one recurring pattern:
pure(x) <*> y. In fact, it is so common that its useful to define an extra
operator for it. If we look at the type, we can see that its very similar to
a map function it takes a function of type A -> B and a parser of type A,
and returns a parser of type B:
infix operator </> { precedence 170 }
func </> <Token, A, B>(l: A -> B,
r: Parser<Token, A>) -> Parser<Token, B> {
return pure(l) <*> r
}
Now we have defined a lot of useful functions, so its time to start combining some of them into real parsers. For example, if we want to create a
parser that can add two integers, we can now write it in the following way:
let plus: Character = "+"
func add(x: Int)(_: Character)(y: Int) -> Int {
return x + y
}
let parseAddition = add </> number <*> token(plus) <*> number
It is often the case that we want to parse something but ignore the result,
for example, with the plus symbol in the parser above. We want to know
that its there, but we do not care about the result of the parser. We can
define another operator, <*, which works exactly like the <*> operator, except that it throws away the right-hand result after parsing it (thats why
the right angular bracket is missing in the operator name). Similarly, we
will also define a *> operator that throws away the left-hand result:
infix operator <*
Now we can write another parser for multiplication. Its very similar to the
parseAddition function, except that it uses our new <* operator to throw
away the "*" after parsing it:
let multiply: Character = "*"
let parseMultiplication = curry(*) </> number <* token(multiply)
<*> number
testParser(parseMultiplication, "8*8")
A Simple Calculator
We can extend our example to parse expressions like 10+4*3. Here, it is
important to realize that when calculating the result, multiplication takes
precedence over addition. This is because of a rule in mathematics (and
programming) thats called order of operations. Expressing this in our
parser is quite natural. Lets start with the atoms, which take the highest
precedence:
typealias Calculator = Parser<Character, Int>
func operator0(character: Character,
evaluate: (Int, Int) -> Int,
operand: Calculator) -> Calculator {
return curry { evaluate($0, $1) } </> operand
<* token(character) <*> operand
}
func pAtom0() -> Calculator { return number }
func pMultiply0() -> Calculator { return operator0("*", *, pAtom0()) }
func pAdd0() -> Calculator { return operator0("+", +, pMultiply0()) }
func pExpression0() -> Calculator { return pAdd0() }
testParser(pExpression0(), "1+3*3")
just a number. To fix this, we can change our operator function to parse either an expression of the form operand operator operand, or expressions
consisting of a single operand:
func operator1(character: Character,
evaluate: (Int, Int) -> Int,
operand: Calculator) -> Calculator {
let withOperator = curry { evaluate($0, $1) } </> operand
<* token(character) <*> operand
return withOperator <|> operand
}
If we want to add some more operators and abstract this a bit further, we
can create an array of operator characters and their interpretation functions, and use the reduce function to combine them into one parser:
typealias Op = (Character, (Int, Int) -> Int)
let operatorTable: [Op] = [("*", *), ("/", /), ("+", +), ("-", -)]
However, our parser becomes notably slow as we add more and more operators. This is because the parser is constantly backtracking: it tries to
parse something, then fails, and tries another alternative. For example,
when trying to parse "1+3*3", first, the "-" operator (which consists of a
"+" expression, followed by a "-" character, and then another "+" expression) is tried. The first "+" expression succeeds, but because no "-" character is found, it tries the alternative: just a "+" expression. If we continue
this, we can see that a lot of unnecessary work is being done.
Writing a parser like the above is very simple. However, it is not very
efficient. If we take a step back and look at the grammar weve defined
using our parser combinators, we could write it down like this (in a pseudogrammar description language):
expression = min
min = add "-" add | add
add = div "+" div | div
div = mul "/" mul | mul
mul = num "*" num | num
To remove a lot of the duplication, we can refactor the grammar like this:
expression = min
min = add ("-" add)?
add = div ("+" div)?
div = mul ("/" mul)?
mul = num ("*" num)?
{ precedence 170 }
We now finally have all the ingredients to once again define our complete
parser. Note that instead of giving just pExpression() to our testParser
function, we combine it with eof(). This makes sure that the parser consumes all the input (an expression followed by the end of the file):
func pExpression() -> Calculator {
return operatorTable.reduce(number, { next, inOp in
op(inOp.0, inOp.1, next)
})
}
testParser(pExpression() <* eof(), "10-3*2")
This parser is much more efficient because it doesnt have to keep parsing
the same things over and over again. In the next chapter, well use this
parsing library to build a small spreadsheet application.
The calculator we built in this example still has significant shortcomings, the most significant being the fact that you can only use each operator once. In the spreadsheet example were going to build in the next
chapter, well remedy this issue.
Chapter 13
In this chapter, well build a parser, an evaluator, and a GUI for a very simple spreadsheet application. A spreadsheet consists of cells which are
organized in rows and columns. Each cell can contain a formula, for example 10*2. As a result of parsing the formula, we construct an abstract
syntax tree, which is a tree structure that describes the formula. Then we
evaluate those formula syntax trees to compute the results for each cell,
and show them in a table view.
The rows in our spreadsheet are numbered (starting from 0), and the
columns are named (from A to Z). The expression C10 refers to the cell in
row 10 and column C. An expression in the form of A0:A10 refers to a list of
cells. In this case, it is the list of cells starting with the first cell in column
A up to (and including) cell 10 in column A.
To build this spreadsheet application, well make use of the parsing library we have built in the previous chapter. Weve already used it there to
build a simple calculator. Now well expand on that to parse the slightly
more complicated formulas in the spreadsheet: in addition to simple arithmetic operations, they also support references to other cells and func-
Sample Code
Contrary to many of the other chapters, this chapter comes with a sample
project instead of a playground, since the project comes with a simple GUI,
and a playground would have been over-challenged with the scope of this
example anyway. Please open the Spreadsheet project to see the whole
example in action.
Parsing
We will divide the parsing phase into two steps: tokenization and parsing.
The tokenization step (also called lexing or lexical analysis) transforms the
input string into a sequence of tokens. In this process, we also deal with
removing whitespace, as well as parsing operator symbols and parentheses, so that we do not have to worry about that in our parser.
The second step parsing then operates on the sequence of tokens returned by the tokenizer, and transforms those into an abstract syntax tree: a tree structure that represents a formula expression.
Tokenization
To produce the list of tokens, we could use the NSScanner class that comes
with Apples Foundation framework. If we want a nice Swift API, wed have
to wrap it first. In addition, we would need to turn off automatic skipping
of whitespace, and handle whitespace ourselves. Therefore, it is much
easier and clearer to not use NSScanner, and instead write a scanner with
the parsing library that we have built.
Now, for each of those cases well define parsers, i.e. functions that consume characters from the input string and return a tuple consisting of a
token and the remainder of the input string. For example, assuming the
formula string 10+2, the parser function for the number case would return
the tuple (Token.Number(10), "+2").
But before we jump into this, lets first define a couple of helper functions that will make this code clearer. They might not make too much
sense on their own, but please bear with us. Youll see them put to good
use in just a moment.
The first of those helper functions is called const and is pretty straightforward:
func const<A, B>(x: A) -> (y: B) -> A {
return { _ in x }
}
You pass a value of type A into const and it returns a constant function,
B -> A, i.e. no matter what you pass into the returned function, it will always return what you passed into const in the first place.
Another useful helper is the tokens function. You pass an array of elements into it, and it constructs a parser that will consume exactly those elements from its input and return a tuple consisting of the array you passed
in and the remainder of elements left:
If this looks like magic to you, please make sure to read the parser combinators chapter, which explains all the basic building blocks of the parsing
library, like the </> and <*> operators.
Using tokens works recursively on the array you pass in: it splits it into
its head (the first element) and tail (the remaining elements), parsing the
head element using the token function we already used in the previous
chapter, combined with a recursive call to the tokens function itself on
the tail.
With this function in place, we can very easily create a string function
that constructs a parser that parses a specific string:
func string(string: String) -> Parser<Character, String> {
return const(string) </> tokens(string.characters)
}
Finally, well introduce a oneOf helper function that lets us combine multiple parsers in a mutually exclusive manner (for example, we want to parse
one of the operators +, -, /, and *):
func oneOf<Token, A>(parsers: [Parser<Token, A>])
-> Parser<Token, A> {
return parsers.reduce(fail(), combine: <|>)
}
With all these helpers in place, lets start with the number case. For this,
we define a parser that parses natural numbers by consuming one or more
decimal digits from the stream of characters and then combines them into
an integer, leveraging the power of the reduce function:
Note how we have already leveraged the oneOf, const, and string helpers
to define the natural number parsers in just a few lines of code.
Now we can define a tNumber function that simply parses a natural number and then wraps it in the Number case to produce a Token:
let tNumber = { Token.Number($0) } </> naturalNumber
> Optional(42)
Next up are operators: we have to parse the string representing the operator and wrap it in the Tokens .Operator case. For each operator thats
defined in the array below, we use the string function to convert it into a
parser, and then combine those parsers for individual operators using the
oneOf function:
let operatorParsers = ["*", "/", "+", "-", ":"].map { string($0) }
let tOperator = { Token.Operator($0) } </> oneOf (operatorParsers)
For references, we have to parse a single capital letter followed by a natural number. First we build a capital parser using the charactersFromSet
helper function from the previous chapter:
let capitalSet = NSCharacterSet.uppercaseLetterCharacterSet()
let capital = characterFromSet(capitalSet)
Now we can use the capital parser in conjunction with the naturalNumber
parser to parse a reference. Since we combine two values, we have to
curry the function that constructs the reference:
let tReference = curry { Token.Reference(String($0), $1) }
</> capital <*> naturalNumber
Note that we have to wrap the result of the capital parser in a String, since
it returns a single character.
The punctuation case is very simple as well: its either an opening
parenthesis or a closing parenthesis, which is then wrapped in the
Punctuation case:
let punctuationParsers = ["(", ")"].map { string($0) }
let tPunctuation = { Token.Punctuation($0) }
</> oneOf(punctuationParsers)
Finally, function names (such as SUM) consist of one or more capital letters,
which are converted into a string and wrapped in the FunctionName case:
let tName = { Token.FunctionName(String($0)) }
</> oneOrMore(capital)
Now we have in place all the functions that we need to generate a stream
of tokens for our formula expressions. Since we want to ignore any kind
of whitespace in those formulas, we define one more helper function,
ignoreLeadingWhitespace, that eats up whitespace between tokens:
let whitespaceSet = NSCharacterSet.whitespaceAndNewlineCharacterSet()
let whitespace = characterFromSet(whitespaceSet)
func ignoreLeadingWhitespace<A>(p: Parser<Character, A>)
-> Parser<Character, A> {
return zeroOrMore(whitespace) *> p
}
Thats all there is to the tokenizer. Now we can run it on a sample expression:
parse(tokenize(), "1+2*3+SUM(A4:A6)")
Parsing
From the list of tokens generated by our tokenizer, we now create an expression. Expressions can be either numbers, references (to another cell),
binary expressions with an operator, or function calls. We can capture
that in the following recursive enum, which is the abstract syntax tree for
spreadsheet expressions:
protocol ExpressionLike {
func toExpression() -> Expression
}
enum Expression {
case Number(Int)
case Reference(String, Int)
case BinaryExpression(String, ExpressionLike, ExpressionLike)
Lets start with parsing numbers. When trying to parse a token to a number
expression, two things can happen: either the parsing succeeds in case
the token was a number token, or it fails for all other cases. To construct
our number parser, well define a helper function, optionalTransform, that
lets us transform the parsed token to an expression, or return nil in case
the token cant be transformed into a number expression:
func optionalTransform<A, T>(f: T -> A?) -> Parser<T, A> {
return { f($0)! } </> satisfy { f($0) != nil }
}
Now we can define the number parser. Note that the Number case is used
twice: the first instance is the Token.Number case, and the second instance
is the Expression.Number case:
let pNumber: ExpressionParser = optionalTransform {
switch $0 {
We can now apply this parser to both numbers and references to see that
it works as expected:
parse(pNumberOrReference, parse(tokenize(), "42")!)
> Optional(42)
> Optional(A5)
In our case, the argument of a function call is always a list of cell references in the form of A1:A3. The list parser has to parse two reference tokens separated by a : operator token:
func makeList(l: Expression, r: Expression) -> Expression {
return Expression.BinaryExpression(":", l, r)
}
let pList: ExpressionParser = curry(makeList) </> pReference
<* op(":") <*> pReference
op is a simple helper that takes an operator string and creates a parser
that parses a corresponding operator token, returning the operator string
as a result.
Before we can put everything together to parse a function call, we still
need a function to parse a parenthesized expression:
func parenthesized<A>(p: Parser<Token, A>) -> Parser<Token, A> {
return token(Token.Punctuation("(")) *> p
<* token(Token.Punctuation(")"))
}
A primitive is either a number, a reference, a function call, or another expression wrapped in brackets. For the latter, we need to use the lazy
helper function, which ensures that expressionParser only gets called if
it really needs to be, otherwise we would get stuck in an endless loop.
Now that we can parse primitives, well put them together to parse
products (or divisions for that matter well treat them as equal, as they
have the same operator precedence). A product consists of at least one
factor, followed by zero or more * factor or / factor pairs. Those pairs
can be modeled as:
let pMultiplier = curry { ($0, $1) } </> (op("*") <|> op("/"))
<*> pPrimitive
Now we can parse primitives and products of primitives. The last missing
piece in the puzzle is the addition of one or more of those products. Since
this follows exactly the same pattern as the product itself, well keep it
brief:
let pSummand = curry { ($0, $1) } </> (op("-")
<|> op("+")) <*> pProduct
let pSum = curry(combineOperands) </> pProduct
<*> zeroOrMore(pSummand)
Evaluation
Now that we have an abstract syntax tree (in the form of Expression
values), we can evaluate these expressions into results. For our simple
example, we will assume a one-dimensional spreadsheet (i.e. with
only one column). Its not too hard to extend the example into a twodimensional spreadsheet, but for explanatory purposes, sticking to one
dimension makes things clearer.
In order to make the code easier to read, well make another simplification: we dont cache the results of evaluating cell expressions. In a real
spreadsheet, it would be important to define the order of operations. For
example, if cell A2 uses the value of A1, it is useful to first compute A1 and
cache that result, so that we can use the cached result when computing
A2s
value. Adding this is not hard, but we leave it as an exercise for the
reader for the sake of clarity.
That said, lets start by defining our Result enum. When we evaluate
an expression, there are three different possible results. In the case of
simple arithmetic expressions such as 10+10, the result will be a number,
which is covered by the case IntResult. In the case of an expression
like A0:A10, the result is a list of results, which is covered by the case
ListResult. Finally, it is very possible that there is an error, for example,
when an expression cant be parsed, or when an illegal expression is
used. In this case, we want to display an error, which is covered by the
EvaluationError case:
enum Result {
case IntResult(Int)
case ListResult([Result])
case EvaluationError(String)
}
}
}
Our tokenizer supports the "+", "/", "*", and "-" operators. However, in
our expression enum, theres the BinaryExpression case, which stores
the operator as a String value. Therefore, we need a way to map those
strings into functions that combine two Ints. We define a dictionary,
integerOperators, which captures exactly that. Note that we have to
wrap every operator with the function o to convince the compiler, though
hopefully this will not be necessary in future releases of the language:
func o(f: (Int, Int) -> Int) -> (Int, Int) -> Int {
return f
}
let integerOperators: Dictionary<String, (Int, Int) -> Int> =
[ "+": o(+), "/": o(/), "*": o(*), "-": o(-) ]
Now we can write a function, evaluateIntegerOperator, that, given an operator string op and two expressions, returns a result. As an additional parameter, it gets a function, evaluate, which knows how to evaluate an expression. The operator op is looked up in the integerOperators dictionary,
which returns an optional function with type (Int, Int) -> Int. We use
the map on optionals, and then evaluate the left argument and the right
argument, giving us two results. We finally use the lift function to combine the two results into a new result. Note that if the operator couldnt
be recognized, this returns nil:
func evaluateIntegerOperator(op: String,
l: Expression,
r: Expression,
evaluate: Expression? -> Result)
-> Result? {
return integerOperators[op].map {
lift($0)(evaluate(l), evaluate(r))
}
}
To evaluate the list operator (e.g. A1:A5), we first check if the operator
string is actually the list operator :. Also, because we only support one
column, we want to make sure that both l and r are references, and that
row2 is larger than or equal to row1. Note that we can check all of those
conditions at the same time using a single switch case; in all other cases,
we simply return nil. In the case that all our preconditions are fulfilled,
we map over each cell, and evaluate the result:
func evaluateListOperator(op: String,
l: Expression,
r: Expression,
evaluate: Expression? -> Result)
-> Result? {
switch (op, l, r) {
case (":", .Reference("A", let row1),
.Reference("A", let row2)) where row1 <= row2:
return Result.ListResult(Array(row1...row2).map {
evaluate(Expression.Reference("A", $0))
})
default:
return nil
}
}
Now were ready to evaluate any binary operator. First, we try if the integer
operator succeeds. In case this returns nil, we try to evaluate the list
operator. In case that also doesnt succeed, we return an error:
func evaluateBinary(op: String,
l: Expression,
r: Expression,
evaluate: Expression? -> Result) -> Result {
For now, well support two functions on lists, SUM and MIN, which compute
the sum and the minimum of a list, respectively. Given a function name
and the result of the parameter (we currently only support functions with
exactly one parameter), we switch on the function name and check in both
cases if the parameter is a list result. If yes, we simply compute the sum
or the minimum of the results. Because these lists are not lists with Ints,
but rather lists with Results, we need to lift the operator using the lift
function:
func evaluateFunction(functionName: String,
parameter: Result) -> Result {
switch (functionName, parameter) {
case ("SUM", .ListResult(let list)):
return list.reduce(Result.IntResult(0), lift(+))
case ("MIN", .ListResult(let list)):
return list.reduce(Result.IntResult(Int.max),
lift { min($0, $1) })
default:
return .EvaluationError("Couldn't evaluate function")
}
}
All that evaluateExpression does is switch on the expression and call the
appropriate evaluation functions:
func evaluateExpression(context: [Expression?])
-> Expression? -> Result {
return { (e: Expression?) in
e.map { expression in
let recurse = evaluateExpression(context)
switch (expression) {
case .Number(let x):
return Result.IntResult(x)
case .Reference("A", let idx):
return recurse(context[idx])
case .BinaryExpression(let s, let l, let r):
return evaluateBinary(s, l.toExpression(),
r.toExpression(), recurse)
case .FunctionCall(let f, let p):
return evaluateFunction(f,
recurse(p.toExpression()))
default:
return .EvaluationError("Couldn't evaluate " +
"expression")
}
} ?? .EvaluationError("Couldn't parse expression")
}
}
Note that, in case of a reference, we look up the referenced cell in the context and recursively call ourselves. If the spreadsheet accidentally contains a circular reference (e.g. A0 depends on A1, and A1 depends on A0),
this function will call itself recursively until a stack overflow occurs.
Finally, we can define a convenience function, evaluateExpressions,
which takes a list of optional expressions, and produces a list of results
by mapping evaluateExpression over it:
As stated in the introduction of this chapter and throughout the text, there
are a lot of limitations to the current parser and evaluator. There could be
better error messages, a dependency analysis of the cells, loop detection,
massive optimizations, and much more. But note that we have defined the
entire model layer of a spreadsheet, parsing, and evaluation in about 200
lines. This is the power of functional programming: after thinking about
the types and data structures, many of the functions are fairly straightforward to write. And once the code is written, we can always make it better
and faster.
Furthermore, we were able to reuse many of the standard library functions within our expression language. The lift combinator made it really
simple to reuse any binary function that works on integers. We were able
to compute the results of functions like SUM and MIN using reduce. And with
the parsing library from the previous chapter in place, we quickly wrote
both a lexer and a parser.
The GUI
Now that we have the formula parser and evaluator in place, we can build
a simple GUI around them. Because almost all frameworks in Cocoa are
object oriented, well have to mix OOP and functional programming to get
this working. Luckily, Swift makes that really easy.
We will create an XIB containing our window, which contains a single
NSTableView with one column. The table view is populated by a data source,
and has a delegate to handle the editing. Both the data source and delegate are separate objects. The data source stores the formulas and their
results, and the delegate tells the data source which row is currently being edited. The edited row is then displayed as a formula rather than a
result.
In the initializer, we initialize the formulas with the string "0", and the initial values with the corresponding result (zero):
override init() {
let initialValues = Array(1..<10)
formulas = initialValues.map { _ in "0" }
results = initialValues.map { Result.IntResult($0) }
}
The Delegate
The delegates only task is to let the data source know which cell is currently being edited. Because wed like a loose coupling between the two,
Now, when the row is edited, we just set that property on the data source
and were done:
class SpreadsheetDelegate: NSObject, NSTableViewDelegate {
var editedRowDelegate: EditedRow?
func tableView(aTableView: NSTableView!,
shouldEditTableColumn aTableColumn: NSTableColumn!,
row rowIndex: Int) -> Bool {
editedRowDelegate?.editedRow = rowIndex
return true
}
}
When the window loads, we hook up the delegate with the data source so
that it can notify the data source about edited rows. Also, we register an
observer that will let us know when editing ends. When the notification
for the end of editing is fired, we set the data sources edited row to nil:
override func windowDidLoad()
delegate.editedRowDelegate = dataSource
NSNotificationCenter.defaultCenter().addObserver(
self,
selector: NSSelectorFromString("endEditing:"),
name: NSControlTextDidEndEditingNotification,
object: nil)
}
func endEditing(note: NSNotification) {
if note.object as NSObject === tableView {
dataSource.editedRow = nil
}
}
}
This is all there is to it. We now have a fully working prototype of a working,
single-column spreadsheet. Check out the sample project for the complete example.
Chapter 14
Functors
Thus far, we have seen two different functions named map with the following types:
func map<T, U>(xs: [T], transform: T -> U) -> [U]
func map<T, U>(optional: T?, transform: T -> U) -> U?
Why have two such different functions with the same name? To answer
that question, lets investigate how these functions are related. To begin with, it helps to expand some of the shorthand notation that Swift
uses. Optional types, such as Int?, can also be written out explicitly as
Optional<Int>, in the same way that we can write Array<T> rather than [T].
If we now state the types of the map function on arrays and optionals, the
similarity becomes more apparent:
func mapOptional<T, U>(maybeX: Optional<T>,
transform: T -> U) -> Optional<U>
func mapArray<T, U>(xs: Array<T>, transform: T -> U) -> Array<U>
Both Optional and Array are type constructors that expect a generic type
argument. For instance, Array<T> and Optional<Int> are valid types, but
Array by itself is not. Both of these map functions take two arguments: the
structure being mapped, and a function transform of type T -> U. The map
functions use a function argument to transform all the values of type T to
values of type U in the argument array or optional. Type constructors
such as optionals or arrays that support a map operation are sometimes
referred to as functors.
In fact, there are many other types that we have defined that are indeed functors. For example, we can implement a map function on the Box
and Result types from Chapter 8:
func map<T, U>(box: Box<T>, transform: T -> U) -> Box<U> {
return Box(transform(box.value))
}
func map<T, U> (result: Result<T>, transform: T -> U) -> Result<U> {
switch result {
case let Result.Success(box):
return Result.Success(map(box, transform))
case let Result.Failure(error):
return Result.Failure(error)
}
}
Similarly, the types we have seen for binary search trees, tries, and
parser combinators are all functors. Functors are sometimes described
as containers storing values of some type. The map functions transform
the stored values stored in a container. This can be a useful intuition,
but it can be too restrictive. Remember the Region type that we saw in
Chapter 2?
typealias Region = Position -> Bool
Using this definition of regions, we can only generate black and white
bitmaps. We can generalize this to abstract over the kind of information
we associate with every position:
struct Region<T> {
let value : Position -> T
}
Such regions are a good example of a functor that does not fit well with the
intuition of functors being containers. Here, we have represented regions
as functions, which seem very different from containers.
Almost every generic enumeration that you can define in Swift will be
a functor. Providing a map function gives fellow developers a powerful, yet
familiar, function for working with such enumerations.
Applicative Functors
Many functors also support other operations aside from map. For example,
the parsers from Chapter 12 were not only functors, but also defined the
following two operations:
func pure<Token, A>(value: A) -> Parser<Token, A>
func <*><Token, A, B>(l: Parser<Token, A -> B>,
r: Parser<Token, A>) -> Parser<Token, B>
The pure function explains how to turn any value into a (trivial) parser that
returns that value. Meanwhile, the <*> operator sequences two parsers:
the first parser returns a function, and the second parser returns an argument for this function. The choice for these two operations is no coincidence. Any type constructor for which we can define appropriate pure
and <*> operations is called an applicative functor. To be more precise, a
functor F is applicative when it supports the following operations:
func pure<A>(value: A) -> F<A>
func <*><A,B>(f: F<A -> B>, x: F<A>) -> F<B>
Now the pure function always returns a constant value for every region.
The <*> operator distributes the position to both its region arguments,
which yields a function of type A -> B, and a value of type A. It then
combines these in the obvious manner, by applying the resulting function
to the argument.
Many of the functions defined on regions can be described succinctly
using these two basic building blocks. Here are a few example functions
inspired by Chapter 1 written in applicative style:
func everywhere() -> Region<Bool> {
return pure(true)
}
func invert(region: Region<Bool>) {
return pure(!) <*> region
}
func intersection(region1: Region<Bool>, region2: Region<Bool>) {
return pure(&&) <*> region1 <*> region2
}
This shows how the applicative instance for the Region type can be used
to define pointwise operations on regions.
Applicative functors are not limited to regions and parsers. Swifts
built-in optional type is another example of an applicative functor. The
corresponding definitions are fairly straightforward:
func pure<A>(value: A) -> A? {
return value
}
func <*><A, B>(optionalTransform: (A -> B)?,
optionalValue: A?) -> B? {
if let transform = optionalTransform {
The pure function wraps a value into an optional. This is usually handled
implicitly by the Swift compiler, so its not very useful to define ourselves.
The <*> operator is more interesting: given a (possibly nil) function and a
(possibly nil) argument, it returns the result of applying the function to the
argument when both exist. If either argument is nil, the whole function
returns nil. We can give similar definitions for pure and <*> for the Result
type from Chapter 7.
By themselves, these definitions may not be very interesting, so
lets revisit some of our previous examples. You may want to recall the
addOptionals function, which tried to add two possibly nil integers:
func addOptionals(maybeX: Int?, maybeY: Int?) -> Int? {
if let x = maybeX {
if let y = maybeY {
return x + y
}
}
return nil
}
Using the definitions above, we can give a short and sweet alternative definition of addOptionals:
func addOptionals(maybeX: Int?, maybeY: Int?) -> Int? {
return pure(curry(+)) <*> maybeX <*> maybeY
}
Once you understand the control flow that operators like <*> encapsulate,
it becomes much easier to assemble complex computations in this fashion.
There is one other example from the optionals chapter that we would
like to revisit:
func populationOfCapital(country: String) -> Int? {
if let capital = capitals[country] {
if let population = cities[capital] {
return population * 1000
}
}
return nil
}
The problem is that the result of the first lookup, which was bound to the
capital variable in the original version, is needed in the second lookup.
Using only the applicative operations, we quickly get stuck: there is no
way for the result of one applicative computation (capitals[country]) to
influence another (the lookup in the cities dictionary). To deal with this,
we need yet another interface.
The M-Word
In Chapter 4, we gave the following alternative definition of populationOfCapital:
func populationOfCapital2 (country : String) -> Int? {
return capitals[country] >>= { capital in
cities[capital] >>= { population in
How is this different from the applicative interface? The types are subtly
different. In the applicative <*> operation, both arguments are optionals.
In the >>= operator, on the other hand, the second argument is a function
that returns an optional value. Consequently, we can pass the result of
the first dictionary lookup on to the second.
The >>= operator is impossible to define in terms of the applicative
functions. In fact, the >>= operator is one of the two functions supported
by monads. More generally, a type constructor F is a monad if it defines
the following two functions:
func pure<A>(value: A) -> F<A>
func >>=<A, B>(x: F<A>, f: A -> F<B>) -> F<B>
If the call to either readFile or writeFile fails, the NSError will be logged
in the result. This may not be quite as nice as Swifts optional binding
mechanism, but it is still pretty close.
There are many other applications of monads aside from handling errors. For example, arrays are also a monad:
func pure<A>(value: A) -> [A] {
return [value]
}
func >>=<A, B>(f: A -> [B], xs: [A]) -> [B] {
return flatten(xs.map(f))
}
for y in ys {
result += [(x, y)]
}
}
return result
}
We can now rewrite cartesianProduct to use the monadic >>= operator instead of for loops:
func cartesianProduct2<A, B>(xs: [A], ys: [B]) -> [(A, B)] {
return xs >>= { x in ys >>= { y in [(x, y)] } }
}
The >>= operator allows us to take an element x from the first array, xs;
next, we take an element y from ys. For each pair of x and y, we return the
array [(x, y)]. The >>= operator handles combining all these arrays into
one large result.
While this example may seem a bit contrived, the >>= operator on arrays has many important applications. Languages like Haskell and Python
support special syntactic sugar for defining lists, called list comprehensions. These list comprehensions allow you to draw elements from existing lists and check that these elements satisfy certain properties. They
can all be desugared into a combination of maps, filters, and >>=.
Discussion
Why care about these things? Does it really matter if you know that some
type is an applicative functor or a monad? We think it does.
Consider the parser combinators from Chapter 11. Defining the correct way to sequence two parsers is not easy: it requires a bit of insight
into how parsers work. Yet it is an absolutely essential piece of our library,
without which we could not even write the simplest parsers. If you have
the insight that our parsers form an applicative functor, you may realize
that the existing <*> provides you with exactly the right notion of sequencing two parsers, one after the other. Knowing what abstract operations
your types support can help you find such complex definitions.
Abstract notions, like functors, provide important vocabulary. If you
ever encounter a function named map, you can probably make a pretty good
guess as to what it does. Without a precise terminology for common structures like functors, you would have to rediscover each new map function
from scratch.
These structures give guidance when designing your own API. If you
define a generic enumeration or struct, chances are that it supports a map
operation. Is this something that you want to expose to your users? Is your
data structure also an applicative functor? Is it a monad? What do the operations do? Once you familiarize yourself with these abstract structures,
you see them pop up again and again.
Although it is harder in Swift than in Haskell, you can define generic
functions that work on any applicative functor. Functions such as the </>
operator on parsers were defined exclusively in terms of the applicative
pure and <*> functions. As a result, we may want to redefine them for other
applicative functors aside from parsers. In this way, we recognize common patterns in how we program using these abstract structures; these
patterns may themselves be useful in a wide variety of settings.
The historical development of monads in the context of functional programming is interesting. Initially, monads were developed in a branch of
Mathematics known as category theory. The discovery of their relevance
to Computer Science is generally attributed to Moggi (1991) and later popularized by Wadler (1992a; 1992b). Since then, they have been used by
functional languages such as Haskell to contain side effects and I/O (Peyton Jones 2001). Applicative functors were first described by McBride and
Paterson (2008), although there were many examples already known. A
complete overview of the relation between many of the abstract concepts
described in this chapter can be found in the Typeclassopedia (Yorgey
2009).
Chapter 15
Conclusion
Further Reading
Where to go from here? Because Swift has not been out very long, not
many books have been published covering advanced topics. That said, we
have tried to compile a list of blogs that we have enjoyed reading in the
past months:
Both Rob Napier and Alexandros Salazar have regularly blogged
about all kinds of topics related to functional programming and
Swift.
NSHipster covers some of the more obscure and interesting corners of the Swift language.
Airspeed Velocity provides one of the most comprehensive
overviews of the Swift standard library and its evolution over time.
And finally, we should, of course, mention objc.io. Despite what the
publications name might suggest, Issue 16 was exclusively about
Swift. Furthermore, we expect to publish more articles about Swift
as the popularity of the language continues to grow.
Closure
This is an exciting time for Swift. The language is still very much in its
infancy. Compared to Objective-C, there are many new features borrowed from existing functional programming languages that have the
potential to dramatically change the way we write software for iOS and OS
X.
At the same time, it is unclear how the Swift community will develop.
Will people embrace these features? Or will they write the same code in
Swift as they do in Objective-C, but without the semicolons? Time will tell.
By writing this book, we hope to have introduced you to some concepts
from functional programming. It is up to you to put these ideas in practice
as we continue to shape the future of Swift.
Additional Code
This section consists of two parts. The first part contains functions that
are used multiple times throughout the book (the Standard Library), and
the second part contains additional code for some chapters to make everything compile.
Standard Library
The compose operator provides function composition.
infix operator >>> { associativity left }
func >>> <A, B, C>(f: A -> B, g: B -> C) -> A -> C {
return { x in g(f(x)) }
}
The flip function reverses the order of the arguments of the function
you pass into it.
func flip<A, B, C>(f: (B, A) -> C) -> (A, B) -> C {
return { (x, y) in f(y, x) }
}
The all function takes a list, and checks if a given predicate is true for
every element.
func all<T> (xs : [T], predicate : T -> Bool) -> Bool {
for x in xs {
if !predicate(x) {
return false
}
}
return true
}
The decompose array method splits the array into an optional head and
tail, unless the list is empty.
extension Array {
var decompose : (head: T, tail: [T])? {
return (count > 0) ? (self[0], Array(self[1..<count])) : nil
}
}
The iterateWhile function repeatedly applies a function while the condition holds.
func iterateWhile<A>(condition: A -> Bool,
initialValue: A,
next: A -> A?) -> A {
if let x = next(initialValue) {
if condition(x) {
The >>= operator on optionals is the monadic bind, and can be thought
of as a flatMap.
infix operator >>= {}
func >>=<U, T>(optional: T?, f: T -> U?) -> U? {
if let x = optional {
return f(x)
} else {
return nil
}
}
The Box class is used to box values and as a workaround to the limitations with generics in the compiler.
class Box<T> {
let unbox: T
init(_ value: T) { self.unbox = value }
}
extension NSGraphicsContext {
var cgContext : CGContextRef {
return pointWise(*, l, r)
}
func +(l: CGSize, r: CGSize) -> CGSize {
return pointWise(+, l, r)
}
func -(l: CGSize, r: CGSize) -> CGSize {
return pointWise(-, l, r)
}
func -(l: CGPoint, r: CGPoint) -> CGPoint {
return pointWise(-, l, r)
}
func +(l: CGPoint, r: CGPoint) -> CGPoint {
return pointWise(+, l, r)
}
func *(l: CGPoint, r: CGPoint) -> CGPoint {
return pointWise(*, l, r)
}
extension CGSize {
var point : CGPoint {
return CGPointMake(self.width, self.height)
}
}
func isHorizontalEdge(edge: CGRectEdge) -> Bool {
switch edge {
case .MaxXEdge, .MinXEdge:
return true
default:
return false
}
}
// A 2-D Vector
struct Vector2D {
let x: CGFloat
let y: CGFloat
var point : CGPoint { return CGPointMake(x, y) }
var size : CGSize { return CGSizeMake(x, y) }
}
func *(m: CGFloat, v: Vector2D) -> Vector2D {
return Vector2D(x: m * v.x, y: m * v.y)
}
extension Dictionary {
var keysAndValues: [(Key, Value)] {
var result: [(Key, Value)] = []
for item in self {
result.append(item)
}
return result
}
}
func normalize(input: [CGFloat]) -> [CGFloat] {
let maxVal = input.reduce(0) { max($0, $1) }
return input.map { $0 / maxVal }
}
current = generator.next()
}
mutating func next() -> A? {
if var c = current {
if let x = c.next() {
return x
} else {
current = generator.next()
return next()
}
}
return nil
}
}
func flatMap<A, B>(ls: SequenceOf<A>,
f: A -> SequenceOf<B>) -> SequenceOf<B> {
return join(map(ls, f))
}
func map<A, B>(var g: GeneratorOf<A>, f: A -> B) -> GeneratorOf<B> {
return GeneratorOf { map(g.next(), f) }
}
func map<A, B>(var s: SequenceOf<A>, f: A -> B) -> SequenceOf<B> {
return SequenceOf {
map(s.generate(), f) }
}
func join<A>(s: SequenceOf<SequenceOf<A>>) -> SequenceOf<A> {
return SequenceOf {
JoinedGenerator(map(s.generate()) {
$0.generate()
})
}
}
func +<A>(l: SequenceOf<A>, r: SequenceOf<A>) -> SequenceOf<A> {
return join(SequenceOf([l, r]))
}
func const<A, B>(x: A) -> B -> A {
return { _ in x }
}
func prepend<A>(l: A) -> [A] -> [A] {
return { (x: [A]) in [l] + x }
}
extension String {
var characters: [Character] {
var result: [Character] = []
for c in self {
result += [c]
}
return result
}
var slice: Slice<Character> {
let res = self.characters
return res[0..<res.count]
}
}
extension Slice {
var head: T? {
return self.isEmpty ? nil : self[0]
}
References
Barendregt, H.P. 1984. The Lambda Calculus, Its Syntax and Semantics.
Studies in Logic and the Foundations of Mathematics. Elsevier.
Bird, Richard. 2010. Pearls of Functional Algorithm Design. Cambridge
University Press.
Church, Alonzo. 1941. The Calculi of Lambda-Conversion. Princeton
University Press.
Claessen, Koen, and John Hughes. 2000. QuickCheck: a Lightweight
Tool for Random Testing of Haskell Programs. In ACM SIGPLAN Notices,
268279. ACM Press. doi:10.1145/357766.351266.
Gibbons, Jeremy, and Oege de Moor, ed. 2003. The Fun of Programming. Palgrave Macmillan.
Girard, Jean-Yves. 1972. Interprtation Fonctionelle Et limination
Des Coupures de Larithmtique dordre Suprieur. PhD thesis, Universit Paris VII.
Harper, Robert. 2012. Practical Foundations for Programming
Languages. Cambridge University Press.
Hinze, Ralf, and Ross Paterson. 2006. Finger Trees: a Simple GeneralPurpose Data Structure. Journal of Functional Programming 16 (02): 197
217. doi:10.1017/S0956796805005769.
Hudak, P., and M.P. Jones. 1994. Haskell Vs. Ada Vs. C++ Vs. Awk Vs.
... an Experiment in Software Prototyping Productivity. Research Report
YALEU/DCS/RR-1049. New Haven, CT: Department of Computer Science,
Yale University.
Hutton, Graham. 2007. Programming in Haskell. Cambridge University
Press.
McBride, Conor, and Ross Paterson. 2008. Applicative Programming
with Effects. Journal of Functional Programming 18 (01): 113.
Moggi, Eugenio. 1991. Notions of Computation and Monads. Information and Computation 93 (1): 5592.
Okasaki, C. 1999. Purely Functional Data Structures. Cambridge University Press.
Peyton Jones, Simon. 2001. Tackling the Awkward Squad: Monadic
Input/Output, Concurrency, Exceptions, and Foreign-Language Calls in
Haskell. In Engineering Theories of Software Construction, edited by
Tony Hoare, Manfred Broy, and Ralf Steinbruggen, 180:47. IOS Press.
Pierce, Benjamin C. 2002. Types and Programming Languages. MIT
press.
Reynolds, John C. 1974. Towards a Theory of Type Structure. In Programming Symposium, edited by B.Robinet, 19:408425. Lecture Notes
in Computer Science. Springer.
. 1983. Types, Abstraction and Parametric Polymorphism. Information Processing.
Strachey, Christopher. 2000. Fundamental Concepts in Programming Languages. Higher-Order and Symbolic Computation 13 (1-2):
1149.
Swierstra, S Doaitse. 2009. Combinator Parsing: a Short Tutorial.
In Language Engineering and Rigorous Software Development, 252300.
Springer. doi:10.1.1.184.7953.
Wadler, Philip. 1989. Theorems for Free! In Proceedings of the
Fourth International Conference on Functional Programming Languages
and Computer Architecture, 347359.
. 1992a. Comprehending Monads. Mathematical Structures
in Computer Science 2 (04): 461493.
. 1992b. The Essence of Functional Programming. In POPL
92: Conference Record of the Nineteenth Annual ACM SIGPLAN-SIGACT
Symposium on Principles of Programming Languages, 114. ACM.