try...catch statements. This lets us write the ‘happy path’ in the try section, and then deal with any exceptions in the catch section. This is not a bad thing. It allows us to focus on the task at hand, without having to think about every possible error that might occur. It’s definitely better than littering our code with endless if-statements.
try...catch, it gets tedious checking the result of every function call for unexpected values. Exceptions and
try...catch blocks serve a purpose but they have some issues. And they are not the only way to handle errors. In this article, we’ll take a look at using the ‘Either monad’ as an alternative to
A few things before we continue. In this article, we’ll assume you already know about function composition and currying. If you need a minute to brush up on those, that’s totally OK. And a word of warning, if you haven’t come across things like monads before, they might seem really… different. Working with tools like these takes a mind shift.
Don’t worry if you get confused at first. Everyone does. I’ve listed some other references at the end that may help. But don’t give up. This stuff is intoxicating once you get into it.
A sample problem
Before we go into what’s wrong with exceptions, let’s talk about why they exist. There’s a reason we have things like exceptions and
try…catch blocks. They’re not all bad all of the time.
To explore the topic, we’ll attempt to solve an example problem. I’ve tried to make it at least semi-realistic. Imagine we’re writing a function to display a list of notifications. We’ve already managed (somehow) to get the data back from the server. But, for whatever reason, the back-end engineers decided to send it in CSV format rather than JSON. The raw data might look something like this:
Now, eventually, we want to render this code as HTML. It might look something like this:
To keep the problem simple, for now, we’ll just focus on processing each line of the CSV data. We start with a few simple functions to process the row. The first one we’ll use to split the fields:
Now, this function is oversimplified because this is a tutorial about error handling, not CSV parsing. If there’s ever a comma in one of the messages, this will go horribly wrong. Please do not ever use code like this to parse real CSV data. If you ever do need to parse CSV data, please use a well-tested CSV parsing library.
Once we’ve split the data we want to create an object, where the field names match the CSV headers. We’ll assume we’ve already parsed the header row somehow. Note that we throw an error if the length of the row doesn’t match the header row (and
_.zipObject is a lodash function):
And finally, we take our object and pass it through a template function to get an HTML string:
If we end up with an error, it would also be nice to have a way to print that too:
And once we have all of those in place, we can put them together to create our function that will process each row:
Exceptions: The good parts
So, what’s good about
try...catch? The thing to note is, in the above example, any of the steps in the
try block might throw an error. In
addDateStr() we intentionally throw errors. And if a problem happens, then we simply catch the error and show whatever message the error happens to have on the page. Without this mechanism, the code gets really ugly. Here’s what it might look like without exceptions. Instead of throwing exceptions, we’ll assume that our functions will return null:
As you can see, we end up with a lot of boilerplate if-statements. The code is more verbose. And it’s difficult to follow the main logic. Also, we don’t have a way for each step to tell us what the error message should be, or why they failed. (Unless, that is, we do some trickery with global variables.) So, we have to guess, and explicitly call
showError() if the function returns null. Without exceptions, the code is messier and harder to follow.
But look again at the version with exception handling. It gives us a nice clear separation of the ‘happy path’ and the exception handling code. The try part is the happy path, and the catch part is the sad path (so to speak). All of the exception handling happens in one spot. And we can let the individual functions tell us why they failed. All in all, it seems pretty nice. In fact, I think most of us would consider the first example a neat piece of code. Why would we need another approach?
Problems with try…catch exception handling
The good thing about exceptions is they let you ignore those pesky error conditions. But unfortunately, they do that job a little too well. You just throw an exception and move on. We can work out where to catch it later. And we all intend to put that
try…catch block in place. Really, we do. But it’s not always obvious where it should go. And it’s all too easy to forget one. And before you know it, your application crashes.
Another thing to think about is that exceptions make our code impure. Why functional purity is a good thing is a whole other discussion. But let’s consider one small aspect of functional purity: referential transparency. A referentially-transparent function will always give the same result for a given input. But we can’t say this about functions that throw exceptions. At any moment, they might throw an exception instead of returning a value. This makes it more complicated to think about what a piece of code is actually doing. But what if we could have it both ways? What if we could come up with a pure way to handle errors?
Coming up with an alternative
If we are going to write our own pure error handling code, then we need to always return a value. So, as a first attempt, what if we returned an Error object on failure? That is, wherever we were throwing an error, we just return it instead. That might look something like this:
This is only a very slight improvement on the version without exceptions. But it is better. We’ve moved responsibility for the error messages back into the individual functions. But that’s about it. We’ve still got all of those if-statements. It would be really nice if there was some way we could encapsulate the pattern. In other words, if we know we’ve got an error, don’t bother running the rest of the code.
Both objects define a log function that expects a single string parameter. But they behave differently. The beauty of this is that we can write code that calls
.log(), but doesn’t care which object it’s using. It might be a consoleLogger or an
ajaxLogger. It works either way. For example, the code below would work equally well with either object:
Another example is the
.toString() method on all JS objects. We can write a
.toString() method on any class that we make. So, perhaps we could create two classes that implement
.toString() differently. We’ll call them Left and Right (I’ll explain why in a moment):
Now, let’s create a function that will call
.toString() on those two objects:
Not exactly mind-blowing, I know. But the point is that we have two different kinds of behavior using the same interface — that’s polymorphism. But notice something interesting. How many if-statements have we used? Zero. None. We’ve created two different kinds of behavior without a single if-statement in sight. Perhaps we could use something like this to handle our errors…
Left and right
Getting back to our problem, we want to define a happy path and a sad path for our code. On the happy path, we just keep happily running our code until an error happens or we finish. If we end up on the sad path though, we don’t bother with trying to run the code anymore. Now, we could call our two classes ‘Happy’ and ‘Sad’ to represent two paths. But we’re going to follow the naming conventions that other programming languages and libraries use. That way, if you do any further reading it will be less confusing. So, we’ll call our sad path ‘Left’ and our happy path ‘Right’ just to stick with convention.
Let’s create a method that will take a function and run it if we’re on the happy path, but ignore it if we’re on the sad path:
Then we could do something like this:
We’re getting closer to something useful, but we’re not quite there yet. Our
.runFunctionOnlyOnHappyPath() method returns the
_value property. That’s fine, but it makes things inconvenient if we want to run more than one function. Why? Because we no longer know if we’re on the happy path or the sad path. That information is gone as soon as we take the value outside of Left or Right. So, what we can do instead is return a
Right with a new
_value inside. And we’ll shorten the name while we’re at it. What we’re doing is mapping a function from the world of plain values to the world of Left and Right. So we call the method
With that in place, we can use Left or Right with a fluent-style syntax:
We’ve effectively created two tracks. We can put a piece of data on the right track by calling
new Right() and put a piece of data on the left track by calling new
If we map along the right track, we follow the happy path and process the data. If we end up on the left path though, nothing happens. We just keep passing the value down the line. If we were to say, put an Error in that left track, then we have something very similar to
As we go on, it gets to be a bit of a pain writing ‘a Left or a Right’ all the time. So we’ll refer to the Left and Right combo together as ‘Either’. It’s either a Left or a Right.
Shortcuts for making Either objects
So, the next step would be to rewrite our example functions so that they return an Either. A Left for an Error, or a Right for a value. But, before we do that, let’s take some of the tedium out of it. We’ll write a couple of little shortcuts. The first is a static method called
.of(). All it does is return a new Left or Right. The code might look like this:
To be honest, I find even Left.of() and Right.of() tedious to write. So I tend to create even shorter shortcuts called left() and right():
With those in place, we can start rewriting our application functions:
The modified functions aren’t so different from the old ones. We just wrap the return value in either Left or Right, depending on whether we found an error.
With that done, we can start re-working our main function that processes a single row. We’ll start by putting the row string into an Either with
right(), and then map
splitFields() to split it:
This works just fine, but we get into trouble when we try the same thing with
This is because
zipRow() expects two parameters. But functions we pass into
.map() only get a single value from the
._value property. One way to fix this is to create a curried version of
zipRow(). It might look something like this:
This slight change makes it easier to transform
zipRow() so it will work nicely with
.map() to run
splitFields() is fine, as
splitFields() doesn’t return an Either. But when we get to running zipRow() we have a problem. Calling
zipRow() returns an Either. So, if we use
.map() we end up sticking an Either inside an Either. If we go any further we’ll be stuck unless we run
.map(). This isn’t going to work so well. We need some way to join those nested Eithers together into one. So, we’ll write a new method, called
Now we’re free to un-nest our values:
We’ve made it a lot further. But remembering to call
.join() every time is annoying. This pattern of calling
.join() together is so common that we’ll create a shortcut method for it. We’ll call it .
chain() because it allows us to chain together functions that return Left or Right:
Going back to our railway track analogy,
.chain() allows us to switch rails if we come across an error. It’s easier to show with a diagram though.
With that in place, our code is a little clearer:
Doing something with the values
We’re nearly done reworking our
processRow() function. But what happens when we return the value? Eventually, we want to take different action depending on whether we have a Left or Right. So we’ll write a function that will take different action accordingly:
We’ve cheated and used the inner values of the Left or Right objects. But we’ll pretend you didn’t see that. We’re now able to finish our function:
And if we’re feeling particularly clever, we could write it using a fluent syntax:
Both versions are pretty neat. Not a
try…catch in sight. And no if-statements in our top-level function. If there’s a problem with any particular row, we just show an error message at the end. And note that in
processRow() the only time we mention Left or Right is at the very start when we call
right(). For the rest, we just use the
.chain() methods to apply the next function.
Ap and lift
This is looking good, but there’s one final scenario that we need to consider. Sticking with the example, let’s take a look at how we might process the whole CSV data, rather than just each row. We’ll need a helper function or three:
So, we have a helper function that splits the CSV data into rows. And we get an Either back. Now, we can use
.map() and some lodash functions to split out the header row from data rows. But we end up in an interesting situation…
We have our header fields and data rows all ready to map over with
processRows(). But headerFields and dataRows are both wrapped up inside an Either. We need some way to convert
processRows() to a function that works with Eithers. As a first step, we will curry processRows:
Now, with this in place, we can run an experiment. We have headerFields which is an Either wrapped around an array. What would happen if we were to take headerFields and call
.map() on it with
.map() here calls the outer function of
processRows(), but not the inner one. In other words,
processRows() returns a function. And because it’s
.map(), we still get an Either back. So we end up with a function inside an Either. I gave it away a little with the variable name.
funcInEither is an Either. It contains a function that takes an array of strings and returns an array of different strings. We need some way to take that function and call it with the value inside dataRows. To do that, we need to add one more method to our Left and Right classes. We’ll call it
.ap() because the standard tells us to. The way to remember it is to recall that
ap is short for ‘apply’. It helps us apply values to functions.
The method for the Left does nothing, as usual. And for the Right class, the variable name spells out that we expect the other Either to contain a function:
So, with that in place, we can finish off our main function:
Now, I’ve mentioned this before, but I find
.ap() a little confusing to work with.² Another way to think about it is to say: “I have a function that would normally take two plain values. I want to turn it into a function that takes two Eithers”. Now that we have
.ap(), we can write a function that will do exactly that. We’ll call it
liftA2(), again because it’s a standard name. It takes a plain function expecting two arguments, and ‘lifts’ it to work with ‘Applicatives’. (Applicatives are things that have an
.ap() method and an
.of() method). So,
liftA2() is short for ‘lift applicative, two parameters’.
liftA2() might look something like this:
So, our top-level function would use it like this:
Really? Is that it?
Why is this any better than just throwing exceptions? Well, let’s think about why we like exceptions in the first place. If we didn’t have exceptions, we would have to write a lot of if-statements all over the place. We would be forever writing code along the lines of ‘if the last thing worked keep going, else handle the error’. And we would have to keep handling these errors all through our code. That makes it hard to follow what’s going on. Throwing exceptions allows us to jump out of the program flow when something goes wrong. So we don’t have to write all those if-statements. We can focus on the happy path.
But there’s a catch. Exceptions hide a little too much. When you throw an exception, you make handling the error some other function’s problem. But it’s all too easy to ignore the exception, and let it bubble all the way to the top of the program. The nice thing about Either is that it lets you jump out of the main program flow like you would with an exception. But it’s honest about it. You get either a Right or a Left. You can’t pretend that Lefts aren’t a possibility, eventually, you have to pull the value out with something like an
Now, I know that sounds like a pain. But take a look at the code we’ve written (not the Either classes, the functions that use them). There’s not a lot of exception handling code there. In fact, there’s almost none, except for the
either() call at the end of
processRow(). And that’s the point. With Either, you get pure error handling that you can’t accidentally forget. But without it stomping through your code and adding indentation everywhere.
This is not to say that you should never ever use
try…catch. Sometimes that’s the right tool for the job, and that’s OK. But it’s not the only tool. Using Either gives us some advantages that
try…catch can’t match. So, perhaps give Either a go sometime. Even if it’s tricky at first, I think you’ll get to like it. If you do give it a go though, please don’t use the implementation from this tutorial. Try one of the well-established libraries like Crocks, Sanctuary, Folktale or Monet. They’re better maintained. And I’ve papered over some things for the sake of simplicity here. And if you do give it a go, let me know by sending me a tweet.
- Professor Frisby’s Mostly Adequate Guide to Functional Programming by Brian Lonsdorf (and others)
- The Fantasy Land Specification
- Stroustrup, B., 2012, Bjarne Stroustrup’s C++ Glossary
- This is not helped by the fact that the Fantasyland specification defines
.ap()in a confusing way. It uses the reverse order from the way most other languages define it.
Plug: LogRocket, a DVR for web apps
LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.