two layers: a denotational semantics for pure expres-
sions (including exception-raising ones), and an oper-
ational semantics \on top" that deals with exception
handling, as well as input/output (Section 4).
Informed by this semantics, we show that various ex-
tensions of the basic idea, such as resource-exhaustion
interrupts, can readily be accommo dated; while oth-
ers, such as a \pure" exception handler, are more trou-
blesome (Section 5).
There has b een a small urry of recent prop osals and pap ers
on exception-handling in Haskell [3, 13 , 12]. The distinctive
feature of this pap er is its fo cus on the semantics of the
resulting language. The trick lies in getting the nice fea-
tures of exceptions (eciency, implicit propagation, and the
like) without throwing the baby out with the bath-water
and crippling the language design.
Those less interested in functional programming
per se
may
nevertheless nd interesting our development of the (old)
idea of exceptions-as-values, and the trade-o b etween pre-
cision and p erformance.
2 The status quo ante
Haskell has managed without exceptions for a long time,
so it is natural to ask whether they are either necessary or
appropriate. We briey explore this question, as a way of
setting the scene for the rest of the pap er.
Before we b egin, it is worth identifying three dierent ways
in which exceptions are typically used in languages that sup-
port them:
Disaster recovery
uses an exception to signal a (hop efully
rare) error condition, such as division by zero or an
assertion failure. In a language like ML or Haskell we
may add pattern-match failure, when a function is ap-
plied to a value for which it do es not have a dening
equation (e.g.
head
of the empty list). The program-
mer can usually also raise an exception, using a prim-
itive such as
raise
.
The exception handler typically catches exceptions
from a large chunk of code, and p erforms some kind of
recovery action.
Exception handling used in this way provides a degree
of modularity: one part of a system can protect itself
against failure in another part of the system.
Alternative return.
Exceptions are sometimes used as an
alternative way to return a value from a function,
where no error condition is necessarily implied. An
example might be lo oking up a key in a nite map:
it's not necessarily an error if the key isn't in the map,
but in languages that support exceptions it's not un-
usual to see them used in this way.
The exception handler typically catches exceptions
from a relatively circumscrib ed chunk of code, and
serves mainly as an alternative continuation for a call.
Asynchronous events.
In some languages, an asyn-
chronous external event, such as the programmer typ-
ing \
^C
" or a timeout, are reected into the program-
mer's mo del as an exception. We call such things
asyn-
chronous exceptions
, to distinguish them from the two
previous categories, which are b oth
synchronous ex-
ceptions
.
2.1 Exceptions as values
No lazy functional programming language has so far sup-
ported exceptions, for two apparently persuasive reasons.
Firstly,
lazy evaluation scrambles control ow
. Evaluation
is demand-driven; that is, an expression is evaluated only
when its value is required [14]. As a result, programs don't
have a readily-predictable control ow; the only pro ductive
way to think ab out an expression is to consider the
value
it computes
, not the
way in which the value is computed
.
Since exceptions are typically explained in terms of changes
in control ow, exceptions and lazy evaluation do not appear
very compatible.
Secondly,
exceptions can be explicitly encoded in values
, in
the existing language, so p erhaps exceptions are in any case
unnecessary. For example, consider a function,
f
, that takes
an integer argument, and either returns an integer or raises
an exception. We can encode it in Haskell thus:
data ExVal a = OK a
| Bad Exception
f :: Int -> ExVal Int
f x = ...defn of f...
The
data
declaration says that a value of type
ExVal t
is
either of the form
(Bad ex)
, where
ex
has type
Exception
,
or is of the form
(OK val)
, where
val
has type
t
. The
type signature of
f
declares that
f
returns a result of type
ExVal Int
; that is, either an
Int
or an exception value. In
short,
the exception is encoded into the value returned by
f
.
Any consumer of
f
's result is forced, willy nilly, to rst p er-
form a case analysis on it:
case (f 3) of
OK val -> ...normal case...
Bad ex -> ...handle exception...
There are goo d things ab out this approach: no extension to
the language is necessary; the type of a function makes it
clear whether it can raise an exception; and the type system
makes it imp ossible to forget to handle an exception.
The idea of exceptions as values is very old [10, 18 ]. Subse-
quently it was realised that the exception type constructor,
ExVal
, forms a
monad
[6, 9]. Rather than having lots of
ad
hoc
pattern matches on
OK
and
Bad
, standard monadic ma-
chinery such as Haskell's
do
notation, can hide away much
of the plumbing.
2