Jeremy W. Sherman

stay a while, and listen

A Practical Example of FlatMap

The Swift standard library introduces some unfamiliar concepts if you’re coming from Obj-C and Cocoa. map is one thing, but for some, flatMap seems a bridge too far. It’s a question of taste, and of background, if something comes across as a well-chosen, expressive phrase or if it just seems like status signaling, high-falutin' bullshit.

Well, I’m not going to sort that all out, but I did find myself rewriting an expression using a mix of if let/else into a flatMap chain recently, so I thought I’d share how I rewrote it and why.

If you’re mystified by Optional.flatMap, read on, and you should have a good feel for what that does in a couple minutes.

I’m not going to demystify everything: You still won’t know why it’s called flatMap.

But then, why do we use + for addition? And how do you implement it in terms of a fixed number of bits?

Just because you don’t know a symbol’s etymology or a function’s implementation, that doesn’t mean you can’t make it do useful work for you. If you treat flatMap as an operator written using Roman letters, you can get good value out of it!

Duck, Duck, Goose

Here’s what some deserialization code looked like to start:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
init?(json: JsonApiObject) {
    guard let name = json.attributes["name"] as? String
        , let initials = json.attributes["initials"] as? String
        else { return nil }
    self.name = name
    self.initials = initials
    self.building = json.attributes["building"] as? String
    self.office = json.attributes["office"] as? String
    self.mailStop = json.attributes["mailStop"] as? String
    if let base64 = json.attributes["photoBase64"] as? String
    , let data = Data(base64Encoded: base64) {
        self.photo = UIImage(data: data)
    } else {
        self.photo = nil
    }
}

Notice how you’re trucking along reading, “OK, we set this field, set that field, set that other field, and WHAT THE HECK IS THAT.” The if let bit comes out of left field, breaks your ability to quickly skim the code, and takes some puzzling to sort out. It also leads to repeating the assignment in both branches.

Cleaning This Up

Extract Intention-Revealing Method

To start with, we can take the existing code as-is, yank it out into a helper method, and call that:

1
self.photo = image(fromBase64: json.attributes["photoBase64"] as? String)

This makes the call site in init? read fine, but we’ve just moved the ugly somewhere else.

Take Advantage of Guard

Shifting it into a method dedicated to returning an image does open up using guard let to make the unhappy path clear:

1
2
3
4
5
6
7
8
func image(fromBase64 string: String?) -> UIImage? {
    guard let base64 = string
    , let data = Data(base64Encoded: base64)
    , let photo = UIImage(data: data) else {
        return nil
    }
    return photo
}

Still Too Noisy!

But that’s no real improvement:

  • The return values just restate our return type. They’re noise.
  • The reader has to manually notice that we’re threading each let-bound name into the computation that’s supposed to produce the next one.
  • We’re forced to name totally uninteresting intermediate values just so we have a handle to them to feed into the next computation.

All told, that’s a lot of noise for something that’s conceptually simple and that should be eminently skimmable.

A Pipeline with Escape Hatch

The pipeline we have is:

  • feed in a string
  • transform it into data by decoding it as base64
  • transform that into an image by feeding it into UIImage
  • spit out the image

The trick is, if any of these steps fails – that is, if any step spits out a nil – we just want to bail out and send back a nil immediately. It’s like each step has an escape hatch that short circuits the rest of the pipeline.

Pipeline with Escape Hatch Is Just FlatMap

Well, that’s exactly the behavior that sequencing all these with Optional.flatMap would buy you! Have a look:

1
2
3
4
5
func image(fromBase64 string: String?) -> UIImage? {
    return string
           .flatMap { Data(base64Encoded: $0) }
           .flatMap { UIImage(data: $0) }
}

And if you inlined it, it’d still be eminently readable, because it puts the topic first (“hey, y'all, we’re going to set photo!”), which preserves the flow of the code and its skimmability, and you can quickly skim the pipeline to see how we get that value.

Conclusion

Flatmap very clearly expresses a data transformation pipeline, without extraneous syntax and temporary variables.

We backed into using it in this example for reasons of readability, not for reasons of “I have a hammer! Everything is a nail!”

Sometimes, the new tool really is the right tool.

Appendix: Similar Rewrites

This “assign something depending on something/s else” situation happens a lot. And it can shake out a lot of different ways.

If the expression had been simpler, we could have rewritten it using ?: to eliminate the repeated assignment target. This often shows up with code like:

1
2
3
4
5
6
- if haveThing {
-     x = thing
- } else {
-     x = defaultThing
- }
+ x = haveThing ? thing! : defaultThing

Which, in that common “sub in a default” case, can be further simplified:

1
2
- x = haveThing ? thing! : defaultThing
+ x = thing ?? defaultThing

And if nil is an A-OK default, becomes the wonderfully concise:

1
2
3
- let defaultThing = nil
- x = thing ?? defaultThing
+ x = thing

There’s a similar transform that eliminates guard let stacks by using optional-chaining, but that deserves a bit more of an example, I think.