The Swift standard library introduces some unfamiliar concepts if you’re coming
from Obj-C and Cocoa.
map is one thing, but for some,
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
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
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
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
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:
This makes the call site in
init? read fine, but we’ve just moved the ugly
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
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
- spit out the image
The trick is, if any of these steps fails – that is, if any step spits out
nil – we just want to bail out and send back a
It’s like each step has an escape hatch that short circuits the rest of the
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
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
which preserves the flow of the code and its skimmability,
and you can quickly skim the pipeline to see how we get that value.
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
eliminate the repeated assignment target. This often shows up with code like:
1 2 3 4 5 6
Which, in that common “sub in a default” case, can be further simplified:
nil is an A-OK default, becomes the wonderfully concise:
1 2 3
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.