Adopting map() & reduce() in Swift

This is the third part in a series about Swift for Objective-C developers. Here are links to parts 1 & 2.

Part 1: Swift Optionals for the Objective-C Developer
Part 2: Variable Types in Swift for Objective-C Developers

Follow me on Twitter


Swift brings with it the ideologies of several programming paradigms. To Objective-C developers, the most obvious is likely to be a more functional style approach to solving problems. One of the fundamental aspects of functional programming is abstracting logic so that your code reads as a dictation of what it is doing, not how it is doing it. Unless performance becomes an issue, the implementation details of certain action are typically less important than the code being as declarative as possible.

One of the tools that makes this possible is the use of higher order functions, functions that take other functions and use them to perform transformations on a set of data. These include `map()`, `reduce()`, `filter()`, etc.

Swift includes these higher order functions and as you embrace the language itself it is also important that you begin to emprace them in order to make your code more idiomatic and easier to read and reason about.

The purpose of this article is show some common uses for these functions in your every day programming. We're going to fucus on `map()` and `reduce()` in particular, and explore some basic ideas as well as some more concrete examples. Let's dive in.

If you're a programmer it's very likely that at some point you've had to comma separate a list of strings.

Your first approach to solving this might include initializing an empty string, going through each item in the list, appending the current item to the string, and then adding the comma to separate it from the next item. It'd probably look something like this:

You knew what the output should be as soon as you read "comma separated" and yet, look how many steps it took me to explain it in words. Did any of that really expand on your understanding of what it is that we were doing in this code? No. Because the implementation details don't matter.

All we really want is to say that `commaSeparatedCoffees` is the result of some transformation on `coffees` and we want the code to be as succinct as that statement. This is a textbook use case for the `reduce()` function.

Note that Strings and other value types cannot be compared using `===` and `==` would cause issues if the same string appears in our list more than once. So to avoid this problem, we reduce over the result of `enumerate()` which returns an array of Tuples, where each entry in the array is the element of the original array as well as an Integer that corresponds to its index in the original array. This lets us correctly test whether the current object we're looking at is the last element in the array or not.

And we can refactor this logic into a generic `join()` function like the ones available in other languages.

Similarly, we can use reduce() to add up a list of numbers. This comes in handy quite often.

or more succinctly:

However, usually it's not just a raw list of numbers. The numbers are often encapsulated in another data type. We can use `map()` to extract the numbers from the objects, then `reduce()` to add them together. For example, let's find the average age of a bunch of people.

ometimes, you have an array of array's that contain a particular data type, but you want to collapse or flatten that into a 1D array consisting of all the objects.

Again we can refactor this into a generic method `flatten` that is implemented in terms of reduce()

Sometimes you want to find the unique values within a particular data set. For example, given a list of `Users` who each have a name and a list of things that they like, we want to obtain a list of all the unique likes between all the `Users`.

We can use a combination of `map()` and `reduce()` to achieve this.

  1. First we `map()` the users to their their likes, giving us an array of array's containg the likes.
  2. Next we flatten that list into a single array of likes using the flatten function we wrote earlier.
  3. Now we have our list, but it contains duplicated, so we ca apply `reduce()` to the list with an initial value of an empty set, and the reduce function will add each object to set.
  4. Because sets only contain unique objects, we end up with a unique list of likes.

Many times when implementing functionality such as form validation, you must combine the value of multiple booleans. `reduce()` is perfect for this.

We can even refactor this logic into a more generic function `combine()` that operates in terms of `reduce()`

In practice, you may have a list of options in your app and the state of each is represented by an `OptionState` object.

Given a list of `OptionState` objects, we can perform validation on the interactions with the list of options.

For example, are they all selected?

Or are any of them selected?

Here we apply `map()` first so the reduce() function works with only `Bools` and not `OptionState` objects like in the previous example.

From reading these examples, it should become more clear how using these functions can make your code easier to reason about. You begin declaring *what* you're doing and not *how* you're doing it. As you begin to write more and more code in this manner, additional uses for `map()` and  `reduce()` will come organically. Give it a shot!

If you enjoyed this post, you should follow me on Twitter.