Coping with (Code) Failures by@james_32022

Coping with (Code) Failures

@james_32022James Bowen

Author

Exception handling is annoying. It would be completely unnecessary if everything in the world worked the way it’s supposed to. But of course that would be a fantasy. Haskell can’t change reality. But its error facilities are a lot better than most languages. This week we’ll look at some common error handling patterns. We’ll see a couple clear instances where Haskell has simpler and cleaner code.

Using Nothing

The most basic example we have in Haskell is the `Maybe` type. This allows us to encapsulate any computation at all with the possibility of failure. Why is this better than similar ideas in other languages? Well let’s take Java for example. It’s easy enough to encapsulate `Maybe` when you’re dealing with pointer types. You can use the “null” pointer to be your failure case.

``public MyObject squareRoot(int x) {  if (x < 0) {    return nil;  } else {    return MyObject(Math.sqrt(x));  }}``

But this has a few disadvantages. First of all, null pointers (in general) look the same as regular pointers to the type checker. This means you get no compile time guarantees that ANY of the pointers you’re dealing with aren’t null. Imagine if we had to wrap EVERY Haskell value in a Maybe. We would need to CONSTANTLY unwrap them or else risk tripping a “null-pointer-exception”. In Haskell, once we’ve handled the `Nothing` case once, we can pass on a pure value. This allows other code to know that it will not throw random errors. Consider this example. We check that our pointer in non-null once already in `function1`. Despite this, good programming practice dictates that we perform another check in `function2`.

``public void function1(MyObject obj) {  if (obj == null) {    // Deal with error  } else {    function2(obj);  }}``
``public void function2(MyObject obj) {  if (obj == null) {    // ^^ We should be able to make this call redundant  } else {    // …  }}``

The second sticky point comes up when we’re dealing with non-pointer, primitive values. We often don’t have a good way to handle these cases. Suppose your function returns an `int`, but it might fail. How do you represent failure? It’s not uncommon to see side cases like this handled by using a “sentinel” value, like 0 or -1.

But if the range of your function spans all the integers, you’re a little bit stuck there. The code might look cleaner if you use an enumerated type, but this doesn’t avoid the problem. The same problem can even crop up with pointer values if null is valid in the particular context.

``public int integerSquareRoot(int x) {  if (x < 0) {    return -1;  } else {    return Math.round(Math.sqrt(x));  }}``
``public void (int a) {  int result = integerSquareRoot(a);  if (result == -1) {    // Deal with error  } else {    // Use correct value  }}``

Finally, monadic composition with `Maybe` is much more natural in Haskell. There are many examples of this kind of spaghetti in Java code:

``public Result computation1(MyObject value) {  …}``
``public Result computation2(Result res) {  …}``
``public int intFromResult(Result res) {  …}``
``public int spaghetti(MyObject value) {  if (value != null) {    result1 = computation1(value);    if (result1 != null) {      result2 = computation2(result1);      if (result2 != null) {        return intFromResult(result2);      }    }  }  return -1;}``

Now if we’re being naive, we might end up with a not-so-pretty version ourselves:

``computation1 :: MyObject -> Maybe Resultcomputation2 :: Result -> Maybe ResultintFromResult :: Result -> Int``
``spaghetti :: Maybe MyObject -> Maybe Intspaghetti value = case value of  Nothing -> Nothing  Just realValue -> case computation1 realValue of    Nothing -> Nothing    Just result1 -> case computation2 result1 of      Nothing -> Nothing      Just result2 -> return \$ intFromResult result2``

But as we discussed in our first Monads article, we can make this much cleaner. We’ll compose our actions within the `Maybe` monad:

``cleanerVersion :: Maybe MyObject -> Maybe IntcleanerVersion value = do  realValue <- value  result1 <- computation1 realValue  result2 <- computation2 result1  return \$ intFromResult result2``

Using Either

Now suppose we want to make our errors contain a bit more information. In the example above, we’ll output `Nothing`if it fails. But code calling that function will have no way of knowing what the error actually was. This might hinder our code's ability to correct the error. We'll also have no way of reporting a specific failure to the user. As we’ve explored, Haskell’s answer to this is the `Either` monad. This allows us to attach a value of any type as a possible failure. In this case, we'll change the type of each function. We would then update the functions to use a descriptive error message instead of returning `Nothing`.

``computation1 :: MyObject -> Either String Resultcomputation2 :: Result -> Either String ResultintFromResult :: Result -> Int``
``eitherVersion :: Either String MyObject -> Either String InteitherVersion value = do  realValue <- value  result1 <- computation1 realValue  result2 <- computation2 result1  return \$ intFromResult result2``

Now suppose we want to try to make this happen in Java. How do we do this? There are a few options I’m aware of. None of them are particularly appetizing.

1. Print an error message when the failure condition pops up.
2. Update a global variable when the failure condition pops up.
3. Create a new data type that could contain either an error value or a success value.
4. Add a parameter to the function whose value is filled in with an error message if failure occurs.

The first couple rely on arbitary side effects. As Haskell programmers we aren’t fans of those. The third option would require messing with Java’s template types. These are far more difficult to work with than Haskell’s parameterized types. If we don’t take this approach, we’d need a new type for every different return value.

The last method is a bit of an anti-pattern, making up for the fact that tuples aren’t a first class construct in Java. It’s quite counter-intuitive to check one of your input values for what do as an an output result. So with these options, give me Haskell any day.

Using Exceptions and Handlers

Now that we understand the more “pure” ways of handling error cases in our code, we can deal with exceptions. Exceptions show up in almost every major programming language; Haskell is no different. Haskell has the `SomeException` type that encapsulates possible failure conditions. It can wrap any type that is a member of the Exception typeclass. You'll generally be creating your own exception types.

Generally, we throw exceptions when we want to state that a path of code execution has failed. Instead of returning some value to the calling function, we’ll allow completely different code to handle the error. If this sounds convoluted, that’s because it kind’ve is. In general you want to prefer keeping the control flow as clear as possible. Sometimes though we cannot avoid it.

So let’s suppose we’re calling a function we know might throw a particular exception. We can “handle” that exception by attaching a handler. In Java, you do this pattern like so:

``public int integerSquareRoot(int value) throws NegativeSquareRootException {  ...}``
``public int mathFunction(int x) {  try {    return 2 * squareRoot(x);  } catch (NegativeSquareRootException e) {    // Deal with invalid result  }}``

To handle exceptions in this manner in Haskell, you have to have access to the IO monad. The most general way to handle exceptions is to use the `catch` function. When you call the action that might throw the exception, you include a “handler” function. This function will take the exception as an argument and deal with the case. If we want to write the above example in Haskell, we should first define our exception type. We only need to derive `Show` to also derive an instance for the `Exception` typeclass:

``import Control.Exception (Exception)``
``data MyException = NegativeSquareRootException  deriving (Show)``
``instance Exception MyException``

Now we can write a pure function that will throw this exception in the proper circumstances.

``import Control.Exception (Exception, throw)``
``integerSquareRoot :: Int -> IntintegerSquareRoot x  | x < 0 = throw NegativeSquareRootException  | otherwise = undefined``

While we can throw the exception from pure code, we need to be in the `IO` monad to catch it. We’ll do this with the `catch` function. We’ll use a handler function that will only catch the specific error we’re expecting. It will print the error as a message and then return a dummy value.

``import Control.Exception (Exception, throw, catch)``
``…mathFunction :: Int -> IO IntmathFunction input = do  catch (return \$ integerSquareRoot input) handler  where    handler :: MyException -> IO Int    handler NegativeSquareRootException =       print "Can't call square root on a negative number!" >> return (-1)``

We can also generalize this process a bit to work in different monads. The `MonadThrow` typeclass allows us to specify different exceptional behaviors for different monads. For instance, `Maybe` throws exceptions by using `Nothing`. `Either` uses `Left`, and `IO` will use `throwIO`. When we’re in a general `MonadThrow` function, we throw exceptions with `throwM`.

``callWithMaybe :: Maybe IntcallWithMaybe = integerSquareRoot (-5) -- Gives us `Nothing```
``callWithEither :: Either SomeException IntcallWithEither = integerSquareRoot (-5) -- Gives us `Left NegativeSquareRootException```
``callWithIO :: IO IntcallWithIO = integerSquareRoot (-5) -- Throws an error as normal``
``integerSquareRoot :: (MonadThrow m) => Int -> m IntintegerSquareRoot x  | x < 0 = throwM NegativeSquareRootException  | otherwise = ...``

There is some debate about whether the extra layers of abstraction are that helpful. There is a strong case to be made that if you’re going to be using exceptional control flow, you should be using `IO` anyway. But using `MonadThrow` can make your code more extensible. Your function might be usable in more areas of your codebase. I’m not too opinionated on this topic (not yet at least). But there are certainly some strong opinions within the Haskell community.

Summary

Error handling is tricky business. A lot of the common programming patterns around error handling are annoying to write. Luckily, Haskell has several different means of doing this. In Haskell, you can express errors using simple mechanisms like `Maybe` and `Either`. Their monadic behavior gives you a high degree of composability. You can also throw and catch exceptions like you can in other languages. But Haskell has some more general ways to do this. This allows you to be agnostic to how functions within your code handle errors.

New to Haskell? Amazed by its awesomeness and want to try? Download our Getting Started Checklist! It has some awesome tools and instructions for getting Haskell on your computer and starting out.

Have you tried Haskell but want some more practice? Check out our Recursion Workbook for some great content and 10 practice problems!

And stay tuned to the Monday Morning Haskell blog!