In some previous posts, I wrote about treating exceptions as a system wide concern. In this post, I extend that idea and talk about distinguishing between exceptional behaviour and just code that didn’t return what you wanted.
Pure functional languages often discourage the use of exceptions because when they are used to control execution flow, they introduce side-affects and violate purity of function. By using the type system to capture exceptional behaviour and dealing with exceptions monadically, it’s much easier to provide that system wide consistently I’ve been talking about.
The norm for object oriented code is to use exceptions to control execution flow. When you have a method that can return
false and throw an exception, it might as well be returning three things. It forces clients to have to reason about logic that has nothing to do with the function of the method. It’s complicated and often makes it hard to treat exceptions consistently across the entire application.
So what can we learn from functional programing languages? Exceptions are a fact of life, unexpected things can happen with your code and you still need to deal with them. The subtlety here is that functional languages emphasize the unexpected part with exceptions. They try and discourage you from using exceptions for dealing with known branches of logic and instead use them like Java uses
Errors (ie as non-recoverable). This means thinking of exceptions of exceptional behaviour and not Java’s notion of checked
So how do languages like Scala discourage you using them like Java? They usually offer alternative mechanisms. Scala for example has the
Try classes. These classes allow you to express using the type system, that a method was successful or unsuccessful, independently from the return value. As an additional bonus, because they are monadic, you can deal with exceptional and expected behaviour consistently in code. That means you can use the same structures to process the positive and the negative case without resorting to
Either in Java
For example, let’s say we have a method
uploadExpenses that uploads this months expenses to my online accountant’s web service. It uploads a single expense at a time, so it could fail because of some network problem or if the web service rejects an individual
Expense. Once done, I’d like to produce a report (just using
System.out in our example).
Traditional Exception Throwing
In a traditional exception throwing version below, the
uploadExpenses call can break after only some expenses have been uploaded. With no report, it would be hard to work out which were successfully uploaded. You’re also left to deal with the exceptions. If other code depends on this, it may make sense to propagate the exception to an appropriate system boundary but dealing with exceptions consistently for the entire system is a real challenge.
On the other hand, if we use an
Either we can make the
uploadExpenses call return either a successfully upload
Expense or a tuple detailing the expense that failed to upload along with the reason why. Once we have a list of these, we can process them in the same way to produce our report. The neat thing here is that the exceptional behaviour is encoded in the return type; clients know that this thing could fail and can deal with it without coding alternative logic.
In this way, having the semantics baked into the return types is what forces clients to deal with the exceptional behaviour. Dealing with them monadically ensures that we can deal with them consistently. For a naive implementation, have a look at my gist and for fuller implementations, see Scala’s version or the TotallyLazy and Functional Java versions in Java.