Failables — Unifying error handling

Philip Borlin
4 min readMar 27, 2018

If you open up some code there are usually two states: either there is error handling peppered throughout the code in a distracting way that makes it hard to see what the core algorithm is doing or there is no error handling which assumes the unlikely scenario that nothing ever goes wrong. In this post I will present a pattern that will bring a middle ground to these two states.

If we want to clean up the error handling in our code the first thing we have to get rid of is exceptions. By exception I mean that thing in your language that requires you to use special syntax in order to deal with it. Many languages create something like try/catch as that special syntax. Instead of throwing exceptions we can just return errors in the normal course of things. Many languages only allow you to return one value and so they add the ability to throw exceptions as a workaround and in reality all functions can either return a error (through the throw key word) or a value (through the return keyword).

In this setup we have two possible return types

  • The expected return value or the happy path
  • An error or the sad path

We can create a wrapper that will either contain an error or an expected return value. Let’s call this wrapper a Failable.

A Failable asks two questions: What do I do in the case of a success? What do I do in the case of a failure? This hasn’t really bought us much yet because we had those two questions before and the way we answered them drove us to start questioning whether there was a better way. Let’s look for a better answer.

The Railway Track

The power of the failable occurs when we start string them together. One powerful model is to think of this like a railway track. We have two parallel tracks: The top is the happy path where no errors occur. The bottom is the sad path where an error has occurred. We start on the happy path because we haven’t done anything.

From: https://www.slideshare.net/ScottWlaschin/railway-oriented-programming by
Scott Wlaschin

In the case of the illustration we first Validate and if it is successful we stay on the happy path. If there is an error we drop to the sad path. If we stay on the happy path we do the UpdateDb operation. If we are on the sad path we don’t execute the UpdateDb logic and we pass the original error along. As long as there are no errors we stay on the happy path. If we get an error at any point we go to the sad path and remain there.

Implementing

I like to be language agnostic, because the principles I like to talk about transcend individual programming languages. In that spirit I’ll use a psuedo code for my examples.

There are two methods to the failable. Almost every language/community has some opinion about what these methods (method/function/procedure/etc) should be called, but I am going to overload proceed() and use it for both methods. Proceed() is going to take function and return another Failable. The second version will unwrap the failable return value after the function runs so you don’t end up with a Failable containing another Failable.

Failable proceed(func: (AnyValue) -> AnyValue)

or

Failable proceed(func: (AnyValue) -> Failable)

When you create a new Failable you will have the choice to put an error or a value in.

new Failable(value)

or

new Failable(error)

This will give you your two railroad tracks. If you call proceed() on a Failable that contains an error then the proceed() will not do anything and will just return itself. If you call proceed() on a Failable with a value then it will call the passed in function using the value inside the failable as the input to the function and return a Failable with the new value (or error) inside.

If you want to chain Failable calls together then the second proceed() method will unwrap the result of the first Failable call for you. You may run into some lexical scope issues if you need access to the return values for previous Failable calls. Each language handles this a little differently so that is beyond the scope of this article.

Additional Thoughts

At this point I should point out a dirty little secret. By implementing this pattern you just built something with monadic behavior. Maybe the category theorists will say this isn’t a monad by the strict definition, but from a practical standpoint it is basically a monad.

If you have ever used Scala, you may notice that the Try type implements this pattern. Other languages may have this pattern also, but I am not aware of any other object that has this built in. This pattern is a specific version of what a lot of languages call an Either type. There are quite a few languages that implement that type.

One other concept I have seen in the wild is to add the concept of an empty state (e.g. null). You would add an additional way to construct the Failable type which would allow you to pass in the null.

new Failable(null)

In a Failable with a null value proceed() would not do anything except return the current Failable just like if you passed in an error.

Conclusion

The Failable pattern can provide a powerful way to unify error handling (and possibly null handling) and simplify your code. By collecting potential errors as you execute you can code the happy path without interspersed error handling. At the end of the happy path you can handle errors thus separating error handling from business logic. This also helps get rid of alternate forms of flow control (i.e. try/catch) making your code easier to reason about.

--

--