In my last post I wrote about why I think Swift is not a simple language. After all, if you need powerful, expressive constructs you´ll end up having some degree of complexity in there. My critique was more about how they market it as simple for beginners, when sometimes I have to scratch my head to understand some code. I´m dumb as a rock, that’s for sure, but have been typing code since ´88. I´ve seen a couple lines of code. I shouldn’t struggle with stuff like this.

Exhibit one

I found similar code to this example during a PR review:

let numberAsString: String? = 100

/// ...

let n: Int? = numberAsString.flatMap(Int.init)

Wait, what? flatMap? Isn´t that a function for arrays of arrays, that apply a transform to the whole array, then flattens the result into a single array? Well, yes. The idea behind Array::flatMap is to apply a transform to all elements inside an array, that can contain other arrays, so every element gets transformed.

A small detour: flatMap for Arrays

Let’s look at this example:

let numbers: [Int] = [10, 20, 30, 40]
let numbersPlusOne = numbers.flatMap { $0 + 1 }
numbersPlusOne // [11, 21, 31, 41]

As you can see, using a flat array, flatMap works the same as map: just takes one element of the array at a time, applies the transform (in this case adding one) and returns a new array with all the transformed values.

The real power of flatMap appears when we have an array of arrays:

let numbersGrouped: [[Int]] = [[10, 30], [20, 40]]
let numbersGroupedPlusOne = numbers.flatMap { $0 + 1 }
numbersGroupedPlusOne // [11, 21, 31, 41]

As you can see, here flatMap is ignoring the fact we’re traversing an array of arrays. Just flattens the array, then apply the transform. So the result is the same.

Back to the Optional stuff

OK, soflatMap is also a function on Optionals, not only on Arrays. What is it good for? Let’s look at this example:

let numberAsString: String? = 100

// If we want to create a number from that Optional String, we’ll need to
// 1st unwrap the String to be sure it’s not `nil`, then trying to
// create a new Int
if let s = numberAsString, let n4 = Int(s) {
    n4
}

To get the value inside that Optional<String> and convert into an Int, we need to do it in two steps:

  • unwrap the String to be sure it’s not nil, this is done in if let s = numberAsString
  • then, take that string and create an Int

To avoid this two-step, boring process, they added a function that applies a transform (in this case Int.init which is used to create an Int after making sure there’s something inside the Optional. Why not call it applyIfSomethingInside, checkContentsAndApply or any other fancy name? If any of you know the reasons, I’m all ears at Twitter)

So code is now:

// flatMap accepts a trailing closure and unwraps the String for us

let n1: Int? = numberAsString.flatMap { (unwrappedString: String) -> Int? in
    return Int(unwrappedString)
}

We can simplify that, as there’s only one line in that closure we can get rid of return:

let n1: Int? = numberAsString.flatMap { (unwrappedString: String) -> Int? in
    Int(unwrappedString)
}

And now, as there’s only one parameter we can use the positional $0 parameter like this:

let n1: Int? = numberAsString.flatMap {
    Int($0)
}

But we can even just pass the name of the function we want to apply, reaching our final form:

// This is a more succint way, too succint maybe
let n3: Int? = numberAsString.flatMap(Int.init)

OK, so now I understand how this flatMap works on Optional. Only took me a blog post to understand it…