Mapping and flatMapping optionals for cleaner code

Alberto Salas
Aug 4 · 3 min read
Photo by Max Nelson on Unsplash

At this stage, you might know that Swift Optionals are just enums on steroids:

enum Optional<Wrapped> {
case none
case some(Wrapped)
}

In fact, you can use the long form to use them:

let longFormSomeOptional = Optional<Int>.some(1)
let longFormNoneOptional = Optional<Int>.none

You might also know how to use Optional binding (if let, guard let, and switch), Optional chaining, the nil-coalescing operator, and forcing unwrapping.

But, you might not know some other operations explained below that can make your life easier.


Mapping an Optional

There’s a map operation in the Optional enum that allows us to map its unwrapped value:

func map<U>(_ transform: (Wrapped) throws -> U) rethrows -> U?

This function evaluates the closure when the Optional is not nil, passing the unwrapped value as a parameter. It returns the result of the closure, or just nil when the Optional is nil.

Let’s see an example:

let someNumber: Int? = 2
let noneNumber: Int? = nil
// closure will be evaluated and result1 is Optional(4)
let result1 = someNumber.map { $0 * $0 }
// closure won't be evaluated and result2 is Optional(nil)
let result2 = noneNumber.map { $0 * $0 }

If we use map along with the nil-coalescing operator, we have a great alternative for an ugly var-if-let-else:

let someNumber: Int? = 2
let noneNumber: Int? = nil
// closure will be evaluated and result1 is 4
let result1 = someNumber.map { $0 * $0 } ?? 0
// closure won't be evaluated and result2 is 0
let result2 = noneNumber.map { $0 * $0 } ?? 0

Optional Binding With Map

There’s a particular use of the map function for Optionals that I think is good to know.

If we check the map signature again:

func map<U>(_ transform: (Wrapped) throws -> U) rethrows -> U?

We can see that when U is Void, the map would return nothing (formally Void? or ()?). As a consequence, we can use the map here as Optional binding.

Let’s see an example:

func log(_ message: String) {
print(message)
}
let someMessage: String? = "Hello World"
let noneMessage: String? = nil
// closure will be evaluated
someMessage.map { log($0) }
// closure won't be evaluated
noneMessage.map { log($0) }

We’re not formally mapping the optionals above, but rather using it as an alternative of if-let, in case this fits better in your code.


FlatMapping an Optional

Let’s say the previous transform closure returns an optional. Then, map would return a double optional.

let someString: String? = "2"
let result1 = someString.map { Int($0) } // result1 is Int??

In that case, you might want to flatMap the optional instead, so the closure result is flattened.

func flatMap<U>(_ transform: (Wrapped) throws -> U?) rethrows -> U?

let someString: String? = "2"
let result1 = someString.flatMap { Int($0) } // result1 is Int?

In the same way that I mentioned with the map operation, we can use flatMap with the nil-coalescing operator as an alternative for an ugly var-if-let-else.

let someString: String? = "2"
let noneString: String? = nil
// closure will be evaluated and result1 is 2
let result1 = someString.flatMap { Int($0) } ?? 0
// closure won't be evaluated and result2 is 0
let result2 = noneString.flatMap { Int($0) } ?? 0

Thanks for reading!

Better Programming

Advice for programmers.

Alberto Salas

Written by

iOS Engineer. UX, Agile, Lean, and Software Architecture Hooligan.

Better Programming

Advice for programmers.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade