Errors versus Failures

Albert Nemec
5 min readNov 28, 2017

Even though we dedicate the heart and soul to our applications, we have to accept the fact that some things are meant to fail.

Imagine you are a software. Quite a complex one, with a lot of functionality and duties. You have to feed yourself, keep your house in a habitable state, do the thing you do at work, maintain relationships with people, pay the bills etc.

On a typical Monday, you've already done half of your daily tasks, when suddenly you realise you forgot the bills, you have to pay at post office, at home.

Paying the bills consists of several sub-steps.

  • Sign the bill
  • Hand over the bill to the post lady
  • Pay for it
  • Take conformation paper

As a computer program written imperatively — as a series of steps — you have very little information about how these tasks relate to each other. So you have to check along the process, if everything is alright and if you can proceed to another step.

function signTheBill(bill) {
if (isPresent(bill)) {
return sign(bill);
} else {
throw new Error('bill is missing');
}
}

What is unfortunate about this code is that signTheBill function isn't concerned with just signing. It has to do some extra logic, that isn't presumed by the purpose of the operation. Error checking that is.

Because the step “Hand over the bill” would make no sense if the bill is not there, instead of proceeding you throw an Error that will stop the execution.

How would that look like in our human-machine analogy? You’d probably just freeze, waiting for the day to end so you can start the over.

That doesn’t seem very good, right? Surely there're better ways to spend your time, than standing mid-way to the post office, with expression of a dead fish on your face, getting all sentimental about your forgotten bill.

Better thing to do would probably be saying “fuck it”, ditching all the steps that are related to the bill business and move on to other tasks.

Right there is a difference between an error and failed computation. But what are those two things anyway?

Error

Good old error is just a way, how computer solves unsolvable problem. Program stops running and stays that way, unless it’s restarted by some parent process or person.

But just because you bumped your wrist, doesn’t mean you have to cut off your arm, right? It indeed is a slight overkill. You should try to minimise the impact of the fuck up and get on with your life, as soon as possible.

Failed Computation

In functional environments, you don’t really need to throw errors. Your application is more of a complex system of roads and highways.

Each road is a function, that is usually chained on another function. No instructions, just one giant expression waiting to get evaluated (“waiting to be drove by your car”).

When one of your functions/roads breaks and it’s impossible to evaluate/drive it, you just close that road. Immediately all other roads that are leading from this closed one are inaccessible and we don’t have to worry about them.

What would Jesus do?

Which of those two approaches would Jesus pick? That is the question. And it's not that rhetorical as it sounds. Jesus already made his choice.

To understand Jesus, let's peak into god's absolute programming language, and how failures are handled in there. What is this language, you ask? Well mathematics, obviously.

Let's take a moment to talk about our lord and saviour algebra.

Math is defined in expressions. It’s not imperative. There is no order to execution of for example multiplication operator.

To draw a parallel with how we program, we often use null value to describe a absence of value. (absence of a bill maybe?) Those non-values are what causes impossible scenarios, where we’re trying to operate on value that isn’t there.

Another spook from Category Theory

Category is defined by objects (in our simple example numbers) and arrows (aka morphisms, which define transition from object A to object B. Basically functions).

Formula A -> B in example as 8 (*2) 16. (*2) being the arrow/function/operation which multiplies by two.

How does the category of numbers with corresponding multiplication arrows handle absence of value?

Well, the zero propagates further, having the resulting expression evaluated to zero.

And that is exactly the kind of behaviour we want for our functions. If one part of the pipeline fails to compute, whole expression is done for, no matter how many times we would operate on it later on (with our binary operation of * or actually any pure function).

No Silent treatment

Beware tho! I'm not encouraging you to let failures happen silently. Sure, you should have some mechanism to track your failures. It doesn't really matter, if you want to log your failures into the console, send request to some error tracking service or write to some log file. For us, it's just some side effect.

We know the pattern of Functor's fmap, and therefore question of “where should the side effects happen” is quite trivial for us. They should be hidden in the composition in a way, that no arrow/function has to be directly concerned with them.

const f = g = h = x => x + 1
const
compose = (...fns) => value =>
fns.reduce((acc, fn) => acc ? fn(acc) : acc, value)
const pipeline = compose(f, g, h)
pipeline(4) // 7

So all the magic is hidden in the compose function, and that is the right place to do the failure logging.

Concluding

Obviously, if you're writing your code imperatively, you're not building your system of roads and you have no choice, but to construct these weird objects that'll stop your app.

But considering you are trying to go functional, this is the way you handle fuckups. The whole principal gets even more joyful, if you're decorating your roads with traffic signs, like type annotations.

--

--

Albert Nemec

Civilian writing about programming and strange ideas in a mildly stylized and semi-digestible way.