I've been thoroughly enjoying Swift, especially its more functional components. It's easy to write highly reusable, type-safe, generic functions, and optionals afford a much welcomed feeling of security, allowing us to write programs that feel (and indeed are) really quite safe.
That said, when performing a series of transformations on some data, it's easy to get carried away and write a bunch of deeply nested nil-checks. To illustrate my point, we're going to load an image from a URL in a string and ultimately shove it into a
UIImageView. Here's how that would look:
The code above is incredibly safe; there's no chance of
UIImageView being initialized unless all of the preceding checks pass. Unfortunately, it's not very concise and this kind of deep indentation makes it hard for anyone to read and grasp the flow of the program. This can be reduced to a single-line nil-check by implicitly unwrapping some optionals but it's far less safe and not actually functionally equivalent (please don't do this):
Implicitly unwrapped optionals are dangerous because they force an optional to be unwrapped, so you'll get a runtime error if that optional is actually empty. That's not safe so it's generally something to avoid. Not to mention, readability and cleanliness can quickly go downhill, especially if you have downcasts scattered throughout these expressions.
I find myself writing transformations like these all the time, especially in ViewModels, and it's incredibly frustrating to have those nested checks and casts all over the place. I figured there must be a better way while still keeping the same safety, so I went ahead trying to build a kind of generic "pipeline" operator.
Side note: If you haven't played with generics or custom operators in Swift yet, I urge you to do so. They are super neat. You can read more about generics in the Swift docs, and more about custom operators on NSHipster.
We can (in)fix this!
Infix operators like
- are really just functions that take two arguments, one to the left and one to the right, as opposed to pre- or postfix operators which only take one argument, to the right or left (respectively). I imagined an infix operator that would take some optional on the left and some function on the right. It would first perform a nil check on the optional and then pass it as an argument to the function on the right, ultimately returning the result of that function. This is what I came up with after a bit of tweaking:
Let's walk through this, since generics can make things a bit confusing to read. First we define a new infix operator,
associativity left which allows for the easy chaining that you'll see in a minute.
On the left we will take an optional of type
In, to be referred to as
In is a generic type, so it doesn't matter what it is, so long as it matches the other occurrences of
On the right, we take a function to be referred to as
fn, with signature
(In) -> (Out?). This means that
fn's single argument must be of the same type as the argument to the left of our pipeline operator. The
fn's return type is an optional of type
Out, again a generic type that can be anything so long as it matches other occurrences of
Out. (Swift's compiler is rather clever, I might add.)
Finally we define a return type on our operator, also an optional of type
Out. Again this can be anything, it can even be
Hopefully you're not too confused, maybe an example will clarify things while also blowing your mind a bit. If we were to rewrite the above example using our new pipeline operator, it would look something like this:
The line breaks are optional, but that's kind of beautiful, right? I think so.
imageView is an optional of type
UIImageView. Each function in the series returns an optional so if any step produces a nil result, imageView will be an empty optional. It's functionally equivalent to the first example in that it is entirely as safe as the first example. Pretty cool, huh?
Multiple return types, arguments
Due to some of Swift's fanciness, this pipeline operator is not limited to piping a single value into a single argument function. I thought, "maybe I could return a tuple and pipe that into a multi-argument function? Nah, no way that would work…" but I tried it anyways, and to my utter surprise, it worked! Mind = blown!! Here's an example of that:
Pretty cool, right? This is also a good example of piping to a function with return type
println, to do something with the newly transformed data. In this case, we just want to print it to the console, but maybe you want to send your data to some final function that saves it or sends it to a server.
Is it worth it? (some quick CBA)
No solution comes without drawbacks, however, and it's important to weigh the costs and benefits of a solution like this.
First of all, custom operators must be learned, and as the "Advanced Swift" WWDC video mentions, "they should pay for themselves". That is just to say that the value of a custom operator should outweigh the need for contributors to learn said operator. In this case, I think it's fair.
If you use a pipeline operator extensively, you'll end up defining a lot more functions to be able to chain them together. While this does mean additional code, I think it's important not to conflate "less code" with "better code". The advantage here is that these are pure, stateless functions that define concrete transformations.
Especially as you encounter more complex transformations, it's helpful to break things down into smaller, more testable parts. The pipeline operator encourages that.
Now, that's functional.
Let's see what you've got
There's a boatload of nifty tricks you can do with a pipe operator like this, and by no means do I claim to have found them all. That's why I wanted to write this, to open things up for discussion and see what smarter people might be able to come up with. I'm really curious to see, so feel free to message me on Twitter.