Try swift nyc neem serra header

Map and FlatMap Magic

Do you find yourself creating messy code in order to transform Swift optionals? Do you wish you harnessed the functional power of Swift more? This talk is for you! You’ll learn about creating elegant code with map and flatMap, and some tips and tricks to use this magic to make your code more Swifty.


Introduction

I’m Neem Serra, and I work at Worldwide Technology Asynchrony Labs. I’m excited to talk to you today about using mapping and flatmapping in Swift to make your code more readable and compact.

Functional Programming in Swift

Swift came out about three years ago. People were interested because Swift has a lot of functional programming features. Functional programming avoids changing the states of things, so you don’t have as much mutable data. When you input something into a function, the same inputs will always produce the same outputs.

Scala also has many features of functional programming, so I asked my friends who do Scala for advice on learning it. They said I should start with mapping and flatmapping, and told me to first learn about monads. However, monads are a fairly deep and complex topic! It turns out that you don’t actually have to learn about monads to learn about how to use mapping and flatmapping.

I’m going to introduce you to mapping and flatmapping in an easier and more fun way - using cupcakes!

Mapping in Swift

In our examples, a box of cupcakes can be represented by an array of cupcakes.

Cupcake Model

The Cupcake has two attributes:cake, which is not optional, and frosting, which is optional. Cupcakes have two functions. The frosted() function takes an unfrosted cupcake and returns the same cupcake, but with frosting. The getFlavor() function returns the flavor string. There is also a Frosting class. Frosting has an optional sprinkles attribute, and has a sprinkled() function that takes a Frosting and it returns to you a Frosting with sprinkles on it. That models our cupcake.

How Mapping Works

The first kind of mapping that we will discuss is mapping over a collection, like an array or a dictionary. When you map over something, you loop over the collection: you take every single element out of it, and you apply some function to it, and then you get a return type. For example, suppose I want to frost all the cupcakes in an array. The “old way” would be to use a for loop to go over every single element of this cupcake array, and I would “frost” them all, like this:

Get more development news like this

let cupcakes = [c1, c2, c3, c4, c5, c6]

for cupcake in cupcakes {
	let frostedCupcake = cupcake.frosted()
}

The new way uses a mapping.

let cupcakes = [c1, c2, c3, c4, c5, c6]

let frostedCupcakes = cupcakes.map{$0.frosted()}

$0.frosted() is a closure, or function, where we are using the shorthand syntax, so $0 is the closure’s first argument.

This will return an array of all frosted cupcakes. So it takes an array of unfrosted cupcakes, maps over it all, and frosts every single cupcake.

Mapping to a Different Type

Mapping doesn’t have to return the same type that is input. In the previous example, we took in a type of Cupcake, and returned a type of Cupcake. In this next example, the mapping returns an array of strings: the cupcake flavors.

In the “old way,” we’d have to initialize an array of empty strings, and then loop over the cupcakes as follows:

var cupcakeFlavors : [String] = []

for cupcake in cupcakes {
	let cupcakeFlavor = cupcake.getFlavor()
	cupcakeFlavors.append(cupcakeFlavor)
}

With mapping, we start with the array of cupcakes, and then use this succinct one liner:

let cupcakeFlavors = cupcakes.map{$0.getFlavor()}

This returns an array of flavor strings, for example ["Red", "22 Baby Blue", ...] (the strings here are Taylor Swift songs, just for fun!)

To summarize, mapping is really nice in that it lets you take an entire array, apply a function to it, and get back an array filled with a new type; in this case it would be all strings.

Different Syntax Examples

Let’s get into more detail about how mapping works. The argument for map() is a closure. A closure is basically a function. Here’s an example where the first argument is a cupcake of type Cupcake and it returns to you something of type Cupcake:

cupcakes.map({ (cupcake : Cupcake)->Cupcake in cupcake.frosted()})

Swift is “smart” so it will tolerate various simplifications of this syntax. Here are some other examples with different syntax that all mean the same thing:

cupcakes.map({ (cupcake : Cupcake) in cupcake.frosted()})
cupcakes.map({ cupcake in cupcake.frosted()})
cupcakes.map({ $0.frosted()})

The last line uses the nice, short, syntax for every single argument is passed in. The first argument you pass in could be $0, the second argument is $1, and so on. These different lines of code all do the same thing. Each can be used depending on how readable you want it, or how complicated it is. If the code on the inside of the closure is pretty long and cumbersome you might want to be more explicit about it, but if it’s pretty short and simple, you can write it as in the last line.

Mapping Dictionaries

Our examples involved arrays. If you wanted to map over a dictionary you would do the same thing. For dictionaries, the first argument would be the key, and the second argument would be the value. To get an array of all of the dictionary values, just map $1, which returns an array of all of the cupcake types. Like this:

let cupcakeDictionary = [ c1 : "Red", c2 : "22BabyBlue", c3 : "Style"]
let flavors = cupcakeDictionary.map{$1}

The returned results may be, for example, ["Red", "Style", "22BabyBlue"] - not necessarily the same order as input, because dictionaries are unordered.

Mapping with Optionals

Mapping on optionals is actually slightly different than mapping on arrays. If the number of cupcakes is an optional integer, and I wanted to add two cupcakes to it, I could try to add two to the number of cupcakes, like this:

let numberOfCupcakes : Int ? = 0
let new count = numberOfCupcakes + 2

This results in an error! You can’t add an optional to an integer without unwrapping it. But mapping will let you do this. If you send something into a map it will unwrap it on the inside, turning it into an integer, so two can be added to it, and then it will rewrap it as an optional and return that. This is a nice convenience!

FlatMapping in Swift

Let’s discuss flatmapping now. Flatmapping “flattens” a container, and maps over an array. People sometimes say that flatmapping “sees things in one less dimension.”

Suppose I have two cupcake boxes and want to represent them as an array. I would have a single array that had two arrays of cupcakes in it. If I flatmap this, the return value would be an array of all the individual cupcakes.

let cupcakeBoxes = [[ c1, c2 ], [ c3, c4 ]]
let newCupcakeBox = cupcakeBoxes.flatMap{$0}

The output newCupcakeBox is [ c1, c2, c3, c4 ]. Anytime that you have nested arrays, flatmapping will remove one layer of the nesting.

The “old way” of doing that would be to loop over the arrays, and then add each item into a new, single array. Again, the code is more succinct with flatmapping, a one liner.

You may wonder what the first argument is, $0. It is actually each array. During the flatmapping, the first array is input, and then the second array is input, and some function is applied to each array. Afterwards, the layer of nesting is removed, and just one array is returned.

Suppose you want to double all of the numbers in your cupcakeBoxes array. You might do something like this:

let cupcakeNumbers = [[3, 4], [1, 3]]
cupcakeNumbers.flatMap{$0 * 2}

Remember, though, I said that the first argument is actually an array, and you can’t multiply an array times two! This code will generate an error. Instead, you have to use a map inside the flatmap, like this:

let double = cupcakeNumbers.flatMap{ array in
	array.map { number in
		number * 2
	}
}

FlatMapping and Optionals

Flatmapping is specifically overloaded to deal with optionals. Imagine that you have a box of cupcakes, and some of the slots for the cupcakes are empty. For example, you have a box with space for four cupcakes, and there are only two cupcakes in there. If you were to map over this you would get back an array that would give you two optional cupcake elements and two nil elements. Now, if you were to do a flatmap on this, you would instead get back an array with just the non-nil cupcakes. The cupcakes wouldn’t be of type optional anymore, either, they would be real cupcakes, and no nils. It’s very clean - like this:

let cupcakes : [Cupcake?] = [c1, nil, c2, nil]
print (cupcakes.map{$0})

This outputs [Optional(c1), nil, Optional(c2), nil]

In contrast,

print (cupcakes.flatMap{$0})

Has the output [c1, c2]

The “old way” of getting this would be something like this:

let realCupcakes : [Cupcake] = []
for cupcake in cupcakes {
	if let cupcake = cupcake {
		realCupcakes.append(cupcake)
	}
}

If you look at the difference between the “old way” and the “new way,” you have a one-liner that takes out all of the nils, versus a four-liner that goes through each of the cupcakes, checks if it’s nil, and then appends it to a new array.

You may have nested arrays of optional cupcakes, with some nils in that optional cupcake array. The first flatmap will take out the nesting of the arrays, and the second flatmap will take out all of the nils. So flatmap takes out the outside layer first, and then keeps going inside. The first outside layer was nested arrays, and the next layer is optionals.

Visually Explicit Chaining

I really like mapping and flatmapping because they give you visually explicit chaining.

Here’s an example. I have two boxes of cupcakes similar to what we had before, the optional cupcake boxes. I want to remove all the nil cupcakes, then frost the remaining cupcakes, and then I want to add sprinkles.

We’ll start with two boxes of cupcakes which may contain nils, and cupcakes will be an array of an array of cupcakes, like so:

let box1 = [c1, nil, nil, c2]
let box2 = [c3, c4, nil, c5, c6]
let cupcakes = [box1, box2]

First, I apply flatmap to cupcakes, which combines the two boxes of cupcakes into one box. Next, flatmap is applied again, to remove all of the nils. Then flatmap it again to frost all the cupcakes. Finally, I would get the frosting object so that I could add sprinkles to it. The deliciousnes array would just have the frostings with sprinkles on them, as follows:

let deliciousness = cupcakes.flatMap{$0}
	.flatMap{$0}
	.flatMap{$0.frosted()}
	.flatMap{$0.frosting}
	.map{$0.sprinkled()}

If I wanted to actually see the cupcakes I would have to print them, which would get the entire object back. Remember, though, that we chained all of this so that the return type of the very last map is frosting.

That was a fairly straightforward example. Here’s something more in-depth. Say I have two boxes of cupcakes. I have a little kid who wants a box of cupcakes that’s frosted, and I’m with one of my friends who doesn’t want frosting on their cupcakes. I want all my cupcakes to be in one box so that I only have to hold one box and hold the kid in the other hand. I want all my nil cupcakes removed from it so I could have this tiny box. The kid wants me to add sprinkles to all of the cupcakes. And then at the very end, I need to figure out how many cupcakes do I have with frosting and sprinkles so I can figure out what the upcharge is at the bakery.

So the first thing I would do, I would take the first box and I would only frost the first box:

let box1 = [c1, nil, nil, c2]
let box2 = [c3, c4, nil, c5, c6]
let frostedBox = box1.map{$0.frosted()}

You get something where the first box now has frosting. Now we apply flatmap to combine our boxes, so the result is two frosted cupcakes and four unfrosted cupcakes. A second flatmap removes the nils. The third flatmap returns only cupcakes that have frosting. Then map is applied to add sprinkles. Finally, count is chained to return the number of cupcakes (two) that are frosted with sprinkles.

let cupcakes = [frostedBox, box2]
let cupcakeCount = cupcakes.flatMap{$0}
	.flatMap{$0}
	.flatMap{$0.frosting}
	.map{$0.sprinkled()}
	.count

Now, doing that normally, without mapping and flatmapping, results in very messy code with a for loop and some conditionals. It’s a lot more cumbersome and harder to read. This is a big plus to using flatmapping and mapping - readability and compact code.

Here is another example of why you might use flatmapping or mapping. Suppose my user interface is getting some weird data back from a server. I want to turn everything into integers, even though the data is not all integer typed. I would do a flatmap on this array and any string that could be turned into an integer would be returned. Anything that couldn’t be turned into an integer would just be dropped and not put in the array.

Here’s yet another example, if I wanted to add an optional subview we could do a map on that and if the optional subview didn’t exist, nothing would happen.

Similarly, you could do the same thing with string interpolation:

let numberOfCupcakes : Int? = 0
numberOfCupcakes.map{"There are \($0) cupcakes in the box."} ?? "Unknown #"

The old way would be:

numberOfCupcakes == nil ? "Unknown #" : "There are \(numberOfCupcakes) in the box."

I like using mapping better in this case because I like having the happy case of there are however many cupcakes in the box first. With the old way, you are checking if the number of cupcakes is nil, first, then flowing through to the happy case if it is not nil. It’s a little more readable, in my mind.

Initialization Methods

Lastly, there’s some magic going on with how we could do some shortcuts on initialization of methods.

An initialization method is a type method which is a function which could be used as a closure in this:

let cupcakeCounts = [1,2,3,4,5]
let strings = cupcakeCounts.map(String.init)
vs
let strings = cupcakeCounts.map{String($0)}

This usage is kind of complicated, so there are office hours after the coffee break and we can talk about this more and go into detail about why you could do both of these things, and they do the exact same thing. It’s magical!

But it gets really magical when you have objects that can fail on initialization. For example, you’re sending in a dictionary to something, and if the dictionary doesn’t have an ID then you can’t make an object - something like that. You can apply flatmap on it, and if it fails on initialization then you could filter those objects out. So if I had all of these strings of types that were cupcake icons, and I did a flatmap and tried to make a UI image out of it, but these images didn’t exist, mapping would give me something that said nil, nil. Flatmapping would actually take out all of the nils. Then if you have objects from your server, you’ve got all these dictionaries and they’re not creating real objects, you would find out very easily and they would just be dropped from your array.

In general, there are a whole lot of other tricks. You can get the index path of a cell, index of an element in an array, and you could start using mapping and flatmapping to step up your functional game. It makes your code a lot easier and simpler to use. And that’s it. I’m done, thank you!

Next Up: Advanced Swift #10: Everything You Ever Wanted to Know on Sequence & Collection

General link arrow white

About the content

This talk was delivered live in September 2017 at try! Swift NYC. The video was recorded, produced, and transcribed by Realm, and is published here with the permission of the conference organizers.

Neem Serra

Neem Serra is an iOS developer in the St. Louis area. She teaches and mentors at a variety of non-profit organizations such as Software Carpentry and the Roy Clay Senior Tech Impact web development workshop. As the lead of the Google Women Techmakers group in St. Louis, she started the St. Louis Techies Project to highlight the diversity of technical people in St. Louis. Neem loves to bake, read comics, and host craft nights.

4 design patterns for a RESTless mobile integration »

close