Exceptions in a functional language

Joel has really a bad opinion about exceptions.  He prefers to return errors as return values instead.

Now, most of this error value method / exceptions discussion seems to occur in the C++ world. In my design for my new, mostly functional programming language Babel-17, I would like to have exception handling built into the language. The reason is simply that explicit checking for error values creates messy code which is very hard to read.

So, what are the challenges of introducing exceptions into the language? A major source of problems is that Babel-17 supports lazy and concurrent evaluation. Look at the following code fragment:

val p = (10, lazy(exception "hello"))

val x = fst p

val y = try (if (snd p == 0) then 0 else 1 end) catch _ => 2

val q = (10, exception "hello")

val z = p == q

Here p is a pair that has as second element a lazy computation. Evaluation of this second element is delayed until its value is needed for the computation of y. Not clear are the answers to these questions:

  • Should computing the value x throw an exception, or should x just be assigned the value 10
  • Should computing the value q throw an exception, or should exceptions be allowed to be part of data structures?
  • After computing  y, should p == q hold? What about before y has been computed?

Instinctively, my answers to these questions would be:

  • computing x should not throw an exception, x will be assigned the value 10
  • Computing q should propagate the exception and not compute a pair. Exceptions in data structures are odd, and I normally do not want that.
  • q does not really exist, because its computation throws an exception. p on the other hand really exists. Also, p should not change its value depending on whether y has already been evaluated or not.

There are similar issues with concurrency:

val p = (10, concurrent(exception "hello"))

val x = fst p

One could argue that concurrency should be semantically just the identity function. Then p does not exist, because calculating p throws an exception.
One could also argue that x should evaluate to 10. This would actually be nice for the implementation of concurrency, because evaluation could then proceed in parallel.

Finally, exceptions introduce even without concurrency or laziness the question of non-determinism, if the evaluation order is not fixed:

(exception "A", exception "B")

Should this throw exception “A” or exception “B” ?

Our approach in Babel-17 is as follows: First, we introduce two new types, exceptions and errors. Exceptions can be thrown via “throw p”, where p is an arbitrary expression (note that “throw (throw p)” is legal notation, but does just the same as “throw p”). Values of type exceptions cannot be stored in data structures and propagate automatically to the top of the call stack unless they are

  • caught via try/catch
  • turned into errors by hitting a lazy/concurrent boundary from the inside

Values of type error on the other hand behave very much like normal data; what is special about error values is that they can be created only from exceptions or via the function constructor “error”. Otherwise error values can be passed around and stored in data structures just like any other data.

Lets revisit above sourcecode and see what this means:

val p = (10, lazy(exception "hello"))
/* semantically, p == (10, error "hello") */

val x = fst p
/* x == 10 */

val y = try (if (snd p == 0) then 0 else 1 end) catch _ => 2
/* y == 1, because error "hello" is not equal to 0 */

val q = (10, exception "hello")
/* results in an exception */

val z = p == q
/* will fail, because q is not even defined */

val p = (10, concurrent(exception "hello"))
/* Again, p is semantically the same as "(10, error "hello"). */

val q = (10, error "hello")

val z = p == q
/* z will evaluate to "true" */


6 Responses to “Exceptions in a functional language”

  1. Norbert Says:

    The nice thing about the ‘Exception Monad’ (in Haskell for example) is that the type system will always bring to your attention that an exception might be caused by the code you use and this exception wants to be handled. So you cannot overlook it but only deliberately ignore it. Thats why type systems are so helpful, they always bring you back to the phlegmatic path of understanding, even if you are tired and lazy and the next freeroll starts in a minute…

    • phlegmaticprogrammer Says:

      May I refer you to my earlier post “I Hate Monads” 🙂

      No, seriously: Exceptions in Babel-17 are legit return values for a function just like any other type, integers for example Because Babel-17 is dynamically typed, it would be unnatural to declare exceptions explicitly and to check statically for them.

      Actually, the exception Monad can be compared to checked exceptions in Java. There is a reason why most decent programmers hate checked exceptions in Java … The problem is that they force you to clutter your code with exception related stuff, although the primary reason for introducing exceptions was to get rid of that clutter.

      A good example of why exceptions should not be captured by a type system is given by the division operation: When you calcuate x/0, it should throw a DivisionByZero exception. Now imagine you would have to introduce monads all over your code whereever you use division … How does Haskell handle this problem? As it turns out, you can raise exception in Haskell without using Monads!! The trick here is that you can raise exceptions in pure code, but you can catch them only in Monad code. So…. Basically, exceptions in Haskell run around unchecked and wild just as in Babel-17, but when you actually want to do something about them, you have to declare it. This is just ridiculous. There is nothing phlegmatic about this way of dealing with exceptions, just something very pathetic.

  2. Norbert Says:

    Exceptions are legit values (or objects) in most programming languages, its the ‘throw’ which makes them special as it initiates an abrupt termination. You are right every division would force you into the exception monad, but at least in Haskell this mainly clutters the types not the code (compared to checked exceptions). The best thing of course is to come up with a proof that you won’t divide by zero, then you can safely use a division that does not throw an exception and its legitimate to have code without any error / exception handling. Anything else is just ignoring the complexity of the problem you are dealing with, as Joel says.

    • phlegmaticprogrammer Says:

      Well, in Babel-17 v0.2 there is no throw. And there is no catch. No pun intended 🙂

      In Haskell, modeling division by the exception monad would not only clutter your type, but also your code, because you have to write everything in monad style. This is not very sensible, because if I want to program imperatively (which is what Monad-style basically is) then clearly using Haskell is overkill. So the conclusion is: you cannot model DivisionByZero via the exception monad, because this would be totally impractical. Probably you should read my previous comment more carefully; because it already states that Haskell DOES NOT USE AN EXCEPTION MONAD FOR DIVISION. How the built-in division in Haskell in realized is like this:

      myDiv :: Float -> Float -> Float
      myDiv x 0 = error “Division by zero”
      myDiv x y = x / y

      So, myDiv actually throws an exception; but you do not see it in the type. Or in other words: Haskell admits that it cannot properly type exceptions.

      Exceptions are not complex. If you can handle an exception at a certain point, deal with it. If you don’t know what do to with it, don’t deal with it. It’s that simple.

  3. Norbert Says:

    I agree, the Haskell code above should only be used for prototyping or debugging, unless you have a proper proof around.

    The nice thing about the idea of an exception monad, might not be its name or its myth, but the simple idea that you do not need anything special in a (purely) functional language. It does not have to be built into the language, you can just define it. Keep the core simple.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: