In a recent project I came across a problem that I am very sure many other developers face: Calculating the total distance between locations stored in a list. Imagine, for instance, that you have a custom route on a map represented by a list of CLLocation objects. How do you calculate the total length of the route from the CLLocation objects alone?
The math behind it is simple and there is absolutely no reason to write a story about that: The total distance equals the sum of distances between all connected pairs of locations in the list. For a list of locations [A, B, C, D] this can be expressed as: |AD|=|AB|+|BC|+|CD|.
The implementation, however, can be done in different ways using the library provided function distance(from:) on our location objects.
A boilerplate solution:
With a traditional object oriented approach, this problem is solved with a few variables and an iteration of the locations:
This implementation works exactly how we expect it to and is quite readable and understandable for any developer having a first look at it. But (without the double line breaks) it is 9 lines of code that will most likely be rewritten for any given problem that requires a reference to the next/previous element as well as a variable that will be adjusted during the iteration of the collection (hence the name boilerplate solution).
A recursive solution:
The recursion specialist will quickly realize that this is a recursive problem: We accumulate a temporary result for a part of the recursive list and perform the same action on the remaining part of the list, using the accumulated result. That is why a recursive function may help us save a few lines of boilerplate code:
But just by looking at the recursive function it is safe to say that the one line of code we gain (compared to the boilerplate solution) is not worth the loss of coherence and simplicity that we introduce with the recursive calls — remember that not all developers are equally happy about recursion.
A reduced solution:
What if I told you all of the boilerplate and recursive code could be limited to a single line of code? A pure OO developer would probably not believe me, but with a functional mindset this is, in fact, achievable.
If we think about what we are doing, we are applying a combining function (distance(from:)) on our recursive data structure, using the result of each application of the function in a recursive manner.
This is exactly the definition of what the higher-order function fold does in functional programming¹. Luckily, in Swift we have a similar function to fold: The
reduce(_:_:) function². The reduce function takes an initial result and a closure as parameter, then combines each element of a Sequence using the closure, before returning the final result.
The simplest example is to use the reduce function to sum a list of integers:
[2, 5, 3, 10].reduce(0, +) // Returns 20
In our case, however, we need to be more specific as the
+ function does not really work on
CLLocation objects. Instead we wish to apply the
distance(from:) function. In order to do so, we also need a reference to the previous (or next) element in the list. The reduce function does not automatically give you access to the next element (as with any other iteration), so to deal with this we can use a Tuple as our initial accumulating result, with the first element being the accumulating distance and the second being the previous
(CLLocationDistance, CLLocation?). The location element is optional because there is no previous element in the initial result that we provide to the reduce function.
Using this tuple we can return a new accumulated tuple from our reduce closure by adding the distance to the first element and replacing the location element with the current location. If we were to extract the closure from the reduce function, the definition would look like this:
As can be seen, the closure takes a tuple containing the accumulated distance and the optional previous location, as well as the currently reached location. The
guard statement in the closure makes sure that there is a previous location, but this can be limited further by using the nil coalescing operator to only have one line in the closure:
The closure can easily be used in the reduce function like this
locations.reduce((0.0, nil), closure).0
.0 at the end because the reduce function is returning a tuple and we are interested in the first element (the distance) within it.
The closure is only shown with types like above for simplicity when explaining, but when using it, the reduce function takes care of all the contextual types and we don’t need to write anything other than the references to the tuple as
$0 and the current
CLLocation object as
$1. Hence the final call to reduce can be reduced to a single line like this:
Comparing this solution to the initial boilerplate solution, we find that we have limited all of the code to a single line. Some would argue that the line is practically unreadable for new developers — i partly agree, but with an understanding of the reduce function and tuples I claim that it shouldn’t take many seconds to understand what is going on.
The icing on the cake
For the icing on the cake we can extend lists of
CLLocation objects with a nice computed variable that is easily accessible:
This ultimately allows us to simply just call
.totalDistance on our Array of
The goal of this article was to shed a bit of light on how a higher-order function like reduce can be used to solve the problem of calculating the total distance between locations in a list of
CLLocation objects. With the three different solutions presented, the reader is free to seek inspiration in any of them — my own personal opinion is, however, that the one-line reduce function is the proper choice in order to avoid as much boilerplate code as possible. In any case it is safe to say that the reduce function really did reduce the lines of code 😉
Please feel free to ask if you have any doubts or questions.
If you want to download the full playground with all of the examples and solutions, it can be done by pressing here.
1: If you are not familiar with folding and other higher-order functions I strongly suggest that you read a few articles about it — it will change your perception of programming completely. Since we are Swift developers I can recommend starting with Luna An’s great introduction to simple HOFs in Swift.
2: In functional languages like F# there is a small difference between reduce and fold, but the underlying idea is the same.