Hardware dependency

Adrian Hey ahey@iee.org
Sun, 17 May 1998 15:29:39 +0100 (BST)


On Sun 10 May, Alan Hutchinson wrote: 
> First impulse:
> Have a few system-defined excptions such as
>  - Real overflow
>  - head of empty list
> which are each identified by a unique number,
> and an option for users to extend or overload these with their own, 
> e.g.by a "function" such as
>    except :: Int -> .a
> 
> All these could be caught by another function
>    catch :: Int -> .a
> which the user is free to define as he wishes.  
> If the user does not define it, then there is a default definition:
>    catch _  =  undef
> 
> The "except" function cannot be defined in user code.
> The "catch" function cannot be called in user code.
> 
> If ever an exception is caused, and there is a global case for "catch"
> matching the exception's number, but no local case in scope, then the 
> program's Start function will return the catch value for this number.
> For instance, if "catch" is defined as
>    catch 22  =  "Folly!" +++ undef
> then any exception of number 22 will cause the program to print
>    Folly!
> and then stop without producing a core dump.
> (This contrasts with the Clean 1.1 "abort" and "undef" functions
>  which let the programmer EITHER print a message and dump OR 
>  do neither.)
> 
> If there is a local case of "catch" in scope, then the encompassing
> function will return the value of the corresponding case of "catch": e.g.
> 
>    myFn ::  ... -> String
> 
>    myFn ... = ...
>    where
>      catch 22 = "Help!"
>      ...
> 
> "myFn" will return "Help!" if ever exception 22 occurs while it is being
> evaluated (unless this exception is caught by some inner case of "catch").
> 
>    myFn2 ... = ...
>    where
>      catch 22 = except 23
>      ...
> 
> will cause exception 23 within the function which called "myFn2".
> 
> This mechanism will not cause side effects, and I think it is
> deterministic: any two runs of the same program will behave alike;
> so I think it falls within the functional paradigm.  However,
> the behaviour of a program will depend on whether functions in it
> are declared to be lazy or eager.

I've been a bit busy recently, so I've only just got round to looking
at what you proposed for exception raising & handling. This looks
as if it would be reasonable from a programmers point of view, apart from 1
_big_ problem. As you say, laziness is the real issue.

The big problem that I can see is that a function can only return 1
value. In a lazy language the function may have returned (part of) its
result before the exception gets raised, by which time its far to late
to 'catch' it using the mechanism you described. I guess this is why you
don't find this kind of exeption handling in any lazy language (that
I'm aware of at least).

The good thing about 'abort' style exceptions is that they don't affect
laziness. The bad thing is (in my opinion) is that aborting the program is
an extreme response, a bit OTT. Unfortunately, its a difficult to see
what else could reasonably be done. Here are a few half baked
ideas of mine which are most probably incoherent, inconsistent and not
original :-)

1- If there is some possibility of a function raising/propogating an
exception, then we (and the compiler) need to know about it, so some
how exceptions should be part of the languages type system, and functions
which may return an exception should reflect this fact in their type.
This would probably be quite complex, in that (ideally) not only would it
be necessary to specify what exceptions a function may raise, but also
what exceptions any of the functions arguments might raise would be
handled by the function (any others being propagated presumably).

2- Why do we (and the compiler) need to know? Because exceptions can only
be caught/handled before a function has returned a result, so any
expression which may result in an exception needs special treatment.

3- What special treatment should these expressions get? Its tempting to
say that they need fully evaluating, but I think this isn't really necessary,
All that needs to be done is to reduce the graph so that all possible
exception generating expressions have been eliminated. Expressions which
can't generate exceptions can be left in the graph. So this implies some,
though not total, loss of laziness. But its, not as bad as it seems.
Currently programmers have to explicitely implement pseudo exception/error
handling through the use of algebraic data types and case analysis, which
forces graph reduction along the lines I just indicated, so no real loss.
It would be nice to have an exception system something like ML's to simplify
programs.

Even this (grossly over simplified) suggestion seems horribly complicated
to me. It certainly isn't just a case of adding an appropriate library
and making slight modifications to syntax. It would probably invlove
adding a whole new extra bit to the type system, strictness analysis,
and some clever tricks in the run time system too, I suspect.

I'm not at all sure that a system like this would be feasible, and even
if it was, it might still be easier to use the 'traditional' explicit
approach to exception/error handling (using algebraic types and case
analysis) most of the time. 

Anybody got any better ideas?

Regards,
-- 
Adrian Hey