In this episode we’ll look at an case where the functional style has some definite benefits over using exceptions, and then go on to try to solve a problem that you will come across that is less easily finessed.

Encapsulating failure

Let’s start this episode with the case where using the functional Either type has some real advantages over exceptions.

Let’s say that I’m processing a potentially huge file, translating every line to a number that I want to sum.

(Ordinarily we could call .sum() on the sequence, but that returns an Int which may overflow too quickly, so here I accumulate to a Long)

We run this for 5 minutes before discovering that some lines are corrupt, so that parseInt throws, aborting the whole process. Sigh. An expedient approach is just to catch the exception, log in place, and substitute a 0.

Hmmm, I’m not sure that is much better, especially with those two nested folds that really don’t mean the same thing unless you close your eyes and imagine very hard. The least we can do is to de-nest them

This is typical of the functional programming approach - push all the nastiness to the outside of the system, leaving nice pure referentially-transparent core algorithms. Now that we’ve had that inspiration, we could of course do the same thing with exception-based code

Which of these implementations is better is open to debate. Pretty much every programmer will be able to read the try version and work out what it does after a while, and I think that it will be more efficient. When you’re used to the declarative style though the eachOrElse version reveals the intention without having to run your brain as a virtual machine. These days I’d probably go with eachOrElse until I needed the performance.

Before we go on, eagle-eyed readers might spot a source of failure that I had missed. Did you?

I’ll give you a couple of minutes.

What I’d failed to spot is that, if this were Java, calls to any methods on a Reader would declare an IOException, and we’d be forced to consider that reading every line might fail. Here the flow of control is inverted, so that the line sequence pokes strings at our code, but even so, every time it does the file could have been deleted or the network gone away. So map can still throw an IOException that we are not revealing by returning Either<Exception, Long>. Our function should be declared as

Dealing with Control Flow

If you try this functional style of error handling, sooner or later you’ll run into places where, well, things get icky. Let’s interpret three strings as ints and add them, the old-fashioned way, with exceptions

but frankly my functional programmer friends hate early returns (which also complicate referential transparency) as much as they hate exceptions, so this is frowned upon.

What is required here is what Haskell calls do-notation, which is a way to sequence expressions only evaluating the next if the previous didn’t ‘fail’. This is of course the role of exceptions and/or early returns in other languages.

Functional programmers value referential transparency so much that Scala programmers are resigned to using their for-comprehensions to solve this problem, and Arrow bends Kotlin’s coroutines to the same end. With Arrow I could write

Now I’m really not enough of a functional programmer to argue that this is as functionally-pure as do-notation, but from the trenches where I stand I can’t see the difference. Combining exceptions with an Either type in this way at least plays to the strengths of the JVM in exception handling, but you may prefer Arrow, even if examining the bytecode it produces is enough to crash IntelliJ.

I think that’s enough for today. In the next installment I hope to make some recommendations for how an API might document its failure modes in a manner that combines the best of checked exceptions and Either types.