Write Yourself a Scheme in 48 Hours/Error Checking and Exceptions

Currently, there are a variety of places within the code where we either ignore errors or silently assign "default" values like  or 0 that make no sense. Some languages – like Perl and PHP – get along fine with this approach. However, it often means that errors pass silently throughout the program until they become big problems, which means rather inconvenient debugging sessions for the programmer. We'd like to signal errors as soon as they happen and immediately break out of execution.

First, we need to import  to get access to Haskell's built-in error functions:

On Debian based systems this needs  installed.

Then, we should define a data type to represent an error:

This is a few more constructors than we need at the moment, but we might as well foresee all the other things that can go wrong in the interpreter later. Next, we define how to print out the various types of errors and make  an instance of  :

Then we define a type to represent functions that may throw a  or return a value. Remember how used an  data type to represent exceptions? We take the same approach here:

Type constructors are curried just like functions, and can also be partially applied. A full type would be  or , but we want to say   and so on. We only partially apply  to , creating a type constructor   that we can use on any data type.

is yet another instance of a monad. In this case, the "extra information" being passed between  actions is whether or not an error occurred. Bind applies its function if the  action holds a normal value, or passes an error straight through without computation. This is how exceptions work in other languages, but because Haskell is lazily-evaluated, there's no need for a separate control-flow construct. If bind determines that a value is already an error, the function is never called.

The  library automatically gives the   monad two other functions besides the standard monadic ones:


 * 1), which takes an   value and lifts it into the   (error) constructor of an
 * 2), which takes an   action and a function that turns an error into another   action. If the action represents an error, it applies the function, which you can use to, e.g. turn the error value into a normal one via   or re-throw as a different error.

In our program, we'll be converting all of our errors to their string representations and returning that as a normal value. Let's create a helper function to do that for us:

The result of calling  is another   action which will always have valid  data. We still need to extract that data from the  monad so it can be passed around to other functions:

We purposely leave  undefined for a   constructor, because that represents a programmer error. We intend to use  only after a , so it's better to fail fast than to inject bad values into the rest of the program.

Now that we have all the basic infrastructure, it's time to start using our error-handling functions. Remember how our parser had previously just returned a string saying "No match" on an error? Let's change it so that it wraps and throws the original :

Here, we first wrap the original  with the   constructor , and then use the built-in function  to return that in our   monad. Since  now returns a monadic value, we also need to wrap the other case in a return function.

Next, we change the type signature of  to return a monadic value, adjust the return values accordingly, and add a clause to throw an error if we encounter a pattern that we don't recognize:

Since the function application clause calls  (which now returns a monadic value) recursively, we need to change that clause. First, we had to change  to, which maps a monadic function over a list of values, sequences the resulting actions together with bind, and then returns a list of the inner results. Inside the  monad, this sequencing performs all computations sequentially but throws an error value if any one of them fails – giving you   on success, or   on failure. Then, we used the monadic "bind" operation to pass the result into the partially applied "apply func", again returning an error if either operation failed.

Next, we change  itself so that it throws an error if it doesn't recognize the function:

We didn't add a return statement to the function application. We're about to change the type of our primitives, so that the function returned from the lookup itself returns a  action:

And, of course, we need to change the numericBinop function that implements these primitives so it throws an error if there's only one argument:

We use an at-pattern to capture the single-value case because we want to include the actual value passed in for error-reporting purposes. Here, we're looking for a list of exactly one element, and we don't care what that element is. We also need to use  to sequence the results of , because each individual call to   may fail with a  :

Finally, we need to change our main function to use this whole big error monad. This can get a little complicated, because now we're dealing with two monads ( (for errors) and  ). As a result, we go back to do-notation, because it's nearly impossible to use point-free style when the result of one monad is nested inside another:

Here's what this new function is doing:


 * 1)   is the list of command-line arguments.
 * 2)   is the result of:
 * 3) taking first argument ;
 * 4) parsing it ;
 * 5) passing it to   (the bind operation has higher precedence than  );
 * 6) calling   on it within the   monad. (Note also that the whole action has type , giving   type  . It has to be, because our   function can only convert errors to  s, and that type must match the type of normal values.)
 * 7) Caught is the result of:
 * 8) calling   on , converting errors to their string representation;
 * 9) calling   to get a   out of this   action;
 * 10) printing the results through.

Compile and run the new code, and try throwing it a couple errors:

$ ghc -package parsec -o errorcheck [../code/listing5.hs listing5.hs] $ ./errorcheck "(+ 2 \"two\")" Invalid type: expected number, found "two" $ ./errorcheck "(+ 2)" Expected 2 args; found values 2 $ ./errorcheck "(what? 2)" Unrecognized primitive function args: "what?"

Some readers have reported that you need to add a  flag to build this example, and presumably all further listings. This tells GHC to build a complete executable, searching out all dependencies listed in the import statements. The command above works on my system, but if it fails on yours, give  a try.