On 12-Jun-1998, Scott Turner <[EMAIL PROTECTED]> wrote:
> At 14:40 1998-06-10 +0100, you wrote:
>
> >Here's a reasonable design for exceptions in Haskell:
>
> >* handle :: (String -> IO a) -> IO a -> IO a
>
> You probably realized more quickly than I how this
> can leak exceptions.
...
> Is this considered a drawback?
This kind of exception handling can "leak" exceptions, but not in the
way you described. Yes, this is a drawback, but it's not nearly as big
a drawback it would be if exceptions could leak in the way you were
talking about. Furthermore, the leakage seems to be inherent to lazy
evaluation, so I'd consider it a drawback of lazy evaluation rather than
a drawback of exception handling. The user can avoid such leakage,
so long as they're willing to lose some laziness.
Details below.
> What I mean is
>
> main = do quotient <- handle (const (return 0)) (return (0 / 0)
> -- Looks plausible
> -- but the exception isn't raised yet.
> print quotient -- Here the expression 0/0 is evaluated
> -- and the exception is raised with no handler.
This is not correct. This example would print out `0' rather than raising
an uncaught division by zero exception.
The reason is basically that the handler is established lazily too.
When `print' evaluates its argument, first the handler is
established, then 0/0 is evaluated, then the handler catches
the exception and returns 0.
This may not have been obvious from SLPJ's original description,
but if your consider the domain-theoretic semantics, it has to be
this way. SLPJ's original description was as follows:
| * handle :: (String -> IO a) -> IO a -> IO a
| (handle h a) tries to perform the action a.
| If doing so delivers a set of exceptional values then
| apply the exception handler h to the string that names
| one of them. It is not defined which of the exceptional
| values is picked.
The result of performing the action `return (0 / 0)'
is a (singleton) set of exceptional values, so the effect
of `handle (const (return 0)) (return (0 / 0))' must be
to apply `const (return 0)' to one of those values,
which in turn has the same effect as `return 0'.
The fact that this is all evaluated lazily doesn't change
the semantics.
If you want to understand the operational semantics in more detail,
then it may perhaps be clearer if you look at my implementation of his `handle'
using `ndset_handle' and `ndset_choose', since that breaks things up into
smaller pieces, seperating out the exception handling from the
nondeterministic choice. But probably the simplest way of seeing it is
to look at the domain-theoretic semantics as outlined above.
So, your example is not a problem. However, it is true that this kind
of exception handling does in a certain sense "leak" exceptions. This
is because `handle' only catches exceptions that occur during the
evaluation of the top level of the value, it doesn't catch exceptions
that occur duing evaluation of the sub-components.
For instance, if we just modify your example slightly,
then we get an example where exceptions really do "leak" out:
main = do list <- handle (const (return [])) (return [0 / 0])
print list
This example will print "[" and then throw an uncaught division by zero
exception.
In order to avoid this, the user needs to force strict
main = do list <- handle (const (return [])) (return e) `hyperseq` e
where e = return [0 / 0]
print list
Here `hyperseq' is a function that is like `seq' except
that it forces complete evaluation, not just evaluation to WHNF
(weak head normal form).
class HyperEval a where
hyperstrict :: (a -> b) -> a -> b
hyperseq :: a -> b -> b
hyperstrict f x = x `hyperseq` f x
instance HyperEval a => HyperEval [a] where
[] `hyperseq` val = val
(x:xs) `hyperseq` val = x `hyperseq` (xs `hyperseq` val)
If we use a version of `handle' where the handler is the second argument
rather than the first (a good idea, IMHO!), then the example could be
written slighly more elegantly, using `hyperstrict' rather than `hyperseq',
as either
main = do list <- hyperstrict handle (return [0/0]) (const (return []))
print list
or if you prefer
main = do list <- (return [0/0])
`hyperstrict handle` (const (return []))
print list
P.S. Is there any reason why something like `HyperEval'
isn't built in to Haskell, or at least include in the
Haskell Library report? Is there any implementation-specific
precedent for something like this in say ghc?
--
Fergus Henderson <[EMAIL PROTECTED]> | "I have always known that the pursuit
WWW: <http://www.cs.mu.oz.au/~fjh> | of excellence is a lethal habit"
PGP: finger [EMAIL PROTECTED] | -- the last words of T. S. Garp.