Exception handling 
Author Message
 Exception handling

Yep, it's me again, trolling away with strange questions... I hope I'm
being more thought provoking than annoying.

What would you say the interaction between exception handling
(catch/throw) and pure functional programming is - when techniques such
as monads or linear types are being used to express imperative styles of
coding?

Normally, we could define an exception to be a special type of value
which all types implicitly include, and which all the atomic functions
but catch just pass straight through themselves. Catch would be a
construct returning the value of it's first parameter unless that value
is an exception, in which case it would apply the second parameter (a
handler function) to it and return that value.

The idea would be that the imperative jump up to an enclosing stack
frame when an exception is thrown is explained as a special exception
value bubbling up to the nearest enclosing catch expression.

With me so far? Good.

What happens if we do this in the presence of monads? I think it would
still work fine, since the monad combinators would, if one of their
arguments threw an exception, throw that exception further up to the
nearest catch.

But what about with linear types like Clean's unique types? Here it
becomes interesting. Because when we throw this exception, we need to
pass any linear state we're using back up to the handler. It will
probably need to return the world, for example, so that the program can
continue executing. And it may need to return other mutable data. Sadly,
this would appear to be impossible to ensure - imagine that a function
dealing with the world performs a division which throws a division by 0
exception. How can the division operator tell what linear values need to
be preserved by passing them back up with the exception object?

It would seem that linear typing and exceptions don't mix.

Does Clean have some kind of exception handling system? I can't read the
manual, sadly, lacking a postscript interepreter I understand on this
machine. If so, how does it WORK?!?

Is there another way of dealing with exceptions in such a language? I
think that linear typing is a very promising way of dealing with
in-place update and operations on the world at large, but this seems to
be the one downfall of it - passing all those values around can cause
problems with control flow.

Or so it appears to me!

ABW



Sun, 14 Jan 2001 03:00:00 GMT  
 Exception handling

Quote:

> Does Clean have some kind of exception handling system?

No.

Quote:
> I can't read the manual, sadly, lacking a postscript interepreter
> I understand on this machine.

The reference manual is also distributed in Adobe Acrobat .PDF
format.  Acrobat can be downloaded free for Windows 95, Windows NT,
MacOS, Linux, Solaris, and some other UNIXes.

Quote:
> Is there another way of dealing with exceptions in such a language?

Linear typing is independent of laziness.  See Henry Baker's papers
on linearity and Mercury, for example.

There isn't any problem with an exception raised inside a computation
that can't change anything.  Such a computation could in Clean
include reading files but not writing them or opening them.  So
Clean could be extended with a sort of `recovery block' scheme
*provided* it wasn't allowed to have unique inputs.  This would
actually make a lot of sense, because you can't roll back output
you've put on paper or sent down a wire.

Exception handling via longjmp() in C, for example, is a problem
because some of your variables have been clobbered and you don't
really know which or how much.  Things are a little bit better
in C++, but a C++ system does have some freedom to rearrange your
code, but it still boils down to "if you get an exception in C++,
you had better treat _every_ variable accessible to the code that
just faulted as garbage, or you WILL be sorry some day."  Note
that exception handling in SML is much less of a worry because
(a) you use mutable variables much much less, so there are fewer
things you have to throw away because they're no longer in a
defined state, and (b) an SML compiler has no freedom to
rearrange things, unlike a C compiler.  (Precisely the same thing
happens in the new Erlang specification, and that's partly a
response to thinking about the problems that exceptions cause.)

So we have a dilemma here:

Horn 1:  have exception handling at the price of often ending up
         in your states where some fraction of your variables are
         no longer defined

Horn 2:  always have variables you can trust, but find some other
         way of dealing with run-time problems.

SML chose Horn 1; Clean chose Horn 2.  Haskell chose to have a form
of exception handling within monads, but the price of that is that
large chunks of your code have to change form, so that's Horn 3.
(This dilemma is a triceratops.)

So I would say that it's not so much that linear typing CAUSES
problems with exceptions but rather that it REVEALS with pitiless
clarity the problems exceptions always had.

As for what Clean does,  I/O code in Clean tends to be full of
(status,result,state') = operation state
followed by "do this if status ok, do that if status failure".



Mon, 15 Jan 2001 03:00:00 GMT  
 Exception handling

Quote:
>Yep, it's me again, trolling away with strange questions... I hope I'm
>being more thought provoking than annoying.

>What would you say the interaction between exception handling
>(catch/throw) and pure functional programming is - when techniques such
>as monads or linear types are being used to express imperative styles of
>coding?

There was a long discussion of these issues recently on the Haskell
mailing list.  Is that list archived somewhere.?

Quote:
>Normally, we could define an exception to be a special type of value
>which all types implicitly include, and which all the atomic functions
>but catch just pass straight through themselves.

What does that mean in the case of binary operations like `+'?
If both operands have exceptional values, which one gets passed?

If you pick a specific one, e.g. the left-hand operand, then this
nails down the order of evaluation, which makes it harder for
optimizers, and it also makes programs harder to reason about,
since `+' is not associative.

If you say it throws a set containing both, then this becomes difficult
to implement (e.g. you can't use the stack-based implementation technique
you suggest below).

A nice solution is to say that it returns a nondeterministic set.
(For details, see the Haskell mailing list archives.)

Quote:
>What happens if we do this in the presence of monads? I think it would
>still work fine, since the monad combinators would, if one of their
>arguments threw an exception, throw that exception further up to the
>nearest catch.

Yep, that's fine.

Quote:
>But what about with linear types like Clean's unique types? Here it
>becomes interesting. Because when we throw this exception, we need to
>pass any linear state we're using back up to the handler. It will
>probably need to return the world, for example, so that the program can
>continue executing. And it may need to return other mutable data. Sadly,
>this would appear to be impossible to ensure - imagine that a function
>dealing with the world performs a division which throws a division by 0
>exception. How can the division operator tell what linear values need to
>be preserved by passing them back up with the exception object?

>It would seem that linear typing and exceptions don't mix.
...
>Is there another way of dealing with exceptions in such a language? I

The very latest development beta-release of the Mercury system includes
an exception handling package that preserves referential transparency.
It provides two different operations for catching an exception.
One of them, called `try', does not allow any arguments with unique
modes (Mercury's equivalent to Clean's linear types) to be passed
to the code enclosed by the handler.  The other, called `try_io',
passes a value of type `io__state' (Mercury's equivalent to `world'
in Clean) to and from the enclosed code; if the enclosed code
throws an exception, then the handler is called with the current
state at the time the exception was thrown.  This allows both the
handler and the enclosed code to perform I/O.

Note that you could store uniquely moded (linearly typed) values
inside the io__state; doing so would allow you to pass them
across exception handlers using `try_io', by storing them in
the io__state before the call to `try_io', and then having
the code enclosed by the handler fetch the value out of the
io__state, do some modifications, and then store the new value
back in the io__state.  However, if an exception occurs
when the uniquely moded value has been removed from the io__state,
the handler won't be able to retrieve it.

We probably ought to have another version of `try'
which takes a `store' rather than an `io__state'.
That would allow you to use this technique to pass
uniquely moded values across exception handlers
without needing to thread an `io__state' through
all the callers.

I've appended below a (slightly edited) copy of some mail
that I sent to the mercury-users mailing list
(<http://www.cs.mu.oz.au/research/mercury/mailinglists.html>)
recently.

--

WWW: <http://www.cs.mu.oz.au/~fjh>  |  of excellence is a lethal habit"

--------------------

Quote:

> Looking over the reference manual, I notice that exceptions are still
> not part of the language.  Fergus posted a patch to provide
> exceptions, but said that there were problems with the semantics.  I
> can see that, but I was wondering whether the debate had moved on?

I think it is fair to say that some progress has been made.
I've continued my attempts to better understand the semantics
of exception handling, and I think I have a reasonable handle on it now.
(I'm currently writing a paper on exception handling in Haskell.)

Quote:
> But if exceptions really are too horrible for words, could somebody
> explain to me why?

Well, one issue is that unless you nail down the operational semantics
more tightly than we want to, if evaluation of a computation could raise
more than one exception then it will be unspecified which one you get.
Even simple things like `throw, fail' may have unspecified behaviour if
you want the compiler to be able to reorder conjunctions.  And even if
you do nail down the operational semantics, you don't want the
declarative semantics to depend on the operational semantics, as would
be the case with the obvious way of modelling exception handling.

However, now that we have committed choice nondeterminism in Mercury,
we won't have any difficulty modelling that one.  So that issue is
no longer a problem.  That is the sense in which progress has been made.

The remaining issue -- the one convinced me to put the issue of
exceptions in Mercury aside for a while -- is the interaction
with unique modes and destructive update.  The combination of
destructive update and exception handling is a real problem,
as the C++ community has recently discovered.  The question is,
if you do destructive update on an object and then throw an exception,
what value will the object have when the exception is caught?
Sometimes you want full commit-or-rollback transaction-like semantics,
where the effects are completely undone on backtracking (either by
trailing any updates as you go, or by making a copy of the object before
you start the operation).  But often this is infeasible (e.g.
if the object is the io__state) or just plain too expensive.

The issue is made more difficult because Mercury has a value-oriented
semantics rather than a state-oriented or object-oriented semantics, so
you can't even really talk about "the current state of the object";
instead, you just have values, which may be either live (referenced) or
dead (in which case their storage may have been reused).  A compiler
that supported compile time garbage collection and structure reuse
might decide to reuse the storage of a dead object to hold a value of a
completely different type.  So allowing compilers to do that would rule
out any kind of semantics where such objects have their value retain
whatever "current state" it had at the time the exception was thrown.

So, the worry is that if we add exception handling support now, before
we have implemented support for structure reuse, and particularly if we
make significant use of it in the standard library, we may be tying our
hands when it comes to implementing structure reuse in the future.
This would not necessarily be the case, but for the time being our
decision was to just remain cautious.

That said, it would be quite possible to provide an exception handling
interface that just did not allow any unique modes to cross the point
where an exception is caught, so that the issue of destructive update
is moot.  The only real worry with this is that if people use it, then
they may end up not being able to take advantage of structure reuse and
destructive update later on.

Actually you'd need two versions, one which didn't allow any unique
modes, and one which allowed only the io__state.  The io__state is
special, in the sense that there is only one such state, and
when you use it you really can consider it as state-oriented
(i.e. imperative) programming.

--

WWW: <http://www.cs.mu.oz.au/~fjh>  |  of excellence is a lethal habit"



Mon, 15 Jan 2001 03:00:00 GMT  
 Exception handling

Quote:
>Exception handling via longjmp() in C, for example, is a problem
>because some of your variables have been clobbered and you don't
>really know which or how much.

Actually the lack of garbage collection in C makes it a significantly
worse problem.

Quote:
>Things are a little bit better
>in C++, but a C++ system does have some freedom to rearrange your
>code, but it still boils down to "if you get an exception in C++,
>you had better treat _every_ variable accessible to the code that
>just faulted as garbage, or you WILL be sorry some day."

This method, known as the "throw and throw away" strategy (because
after you "throw" an exception, you must "throw away" all those
variables), is the simplest way of handling things in C++.  But it is
not the only way.  There has been a lot of discussion on the C++ groups
about different levels of exception safety than different objects can
provide.  "throw and throw away" is the minimal safe level, but it's
possible to implement other strategies for dealing with exceptions.
For example, classes can support a database transaction style of
"commit-or-rollback" semantics, where the class guarantees to restore
the object's state to something consistent if an exception is thrown.

However, I agree that writing exception-safe code in C++ is not an
easy task in general.  In particular, I agree with your comment below:

Quote:
>So I would say that it's not so much that linear typing CAUSES
>problems with exceptions but rather that it REVEALS with pitiless
>clarity the problems exceptions always had.

--

WWW: <http://www.cs.mu.oz.au/~fjh>  |  of excellence is a lethal habit"



Mon, 15 Jan 2001 03:00:00 GMT  
 Exception handling

Quote:

> [...]

> There was a long discussion of these issues recently on the Haskell
> mailing list.  Is that list archived somewhere.?

Yes, it's at

        http://www.dcs.gla.ac.uk/mail-www/haskell/

        - Andreas



Mon, 15 Jan 2001 03:00:00 GMT  
 Exception handling

Quote:

> What does that mean in the case of binary operations like `+'?
> If both operands have exceptional values, which one gets passed?

[sound of Alaric's brain coming out of his ears]

My God - Exceptions Suck

You have a good point there. Lazy evaluation and parallel evaluation
both
present some 'interesting' exception handling cases. YIKES...

Quote:
> A nice solution is to say that it returns a nondeterministic set.
> (For details, see the Haskell mailing list archives.)

Ah, but in my local slang, that's just buffling (= copping out, avoiding
the Real Issue, going for the easy option) since one exception may well
be more interesting than the other - if one side of the + threw a
division by
zero but the other side wanted to alert us of running out of disk space,
we'd be
more interested in the lack of disk space than some puny arithmetic
error with no
global consequence! Then again, maybe the division by zero is more
important if the value
being divided by is your bank balance...

Quote:
> >Is there another way of dealing with exceptions in such a language? I
> The very latest development beta-release of the Mercury system includes
> an exception handling package that preserves referential transparency.
[...]
> state at the time the exception was thrown.  This allows both the
> handler and the enclosed code to perform I/O.

Hmmm, interesting; I'd look into Mercury more if I wasn't so afraid of
Prolog :-)

ABW



Mon, 15 Jan 2001 03:00:00 GMT  
 Exception handling

Man, I hadn't realised this newsreader was set up to call me abw97. How
ugly! My name's really Alaric, and I live in London. Hi.

Quote:
> My God - Exceptions Suck

In retrospect, I guess that means that something like Haskell's "Maybe"
is the best way of handling exceptional situations right now, combined
with the right utility functions to make it easy to use. That way we
have to handle "exceptions" the very instant they emerge from the
functions we call, the type system forces us to not forget about them,
and so on. The Maybe type would need to be expanded so the failure case
contained some useful information as to what failed, perhaps.

On a similar note, Dylan's restart exception things are quite nice and
can fit into a pure functional model well if you have a form of dynamic
binding. For the uninitiated, Dylan's restarts (and, IIRC, EuLisp's
"continuable conditions"?) are like exceptions except that control only
temporarily jumps out of it's lexical scope - when the handler finishes,
execution continues where it left off.

A typical example is division by zero; rather than giving up on what we
were trying to acheive, we invoke the handler - which returns an
acceptable result for the division, maybe a NaN or Inf constant,
depending on what we are trying to achieve and the semantic implications
of division by zero in this context.

If we had constrained dynamic binding with dynamic-let and dynamic-get
constructs, we could just dynamically bind some closures to handler
functions - with names such as "HandleDivZero" - and the division
operator would contain code like:

div q d = q/d
div q 0 = (dynamic-get HandleDivZero) q

We could enclose this like so:

dynamic-let HandleDivZero x = x
   in div a b

Meaning, "in this division, treat a zero divisor as though we were
dividing by 1 instead".

AFAIK, dynamic binding can be "expressed" in terms of pure lambda
calculi as passing implicit parameters down from the dynamic-lets.

ABW



Mon, 15 Jan 2001 03:00:00 GMT  
 Exception handling

Quote:

>Hmmm, interesting; I'd look into Mercury more if I wasn't so afraid of
>Prolog :-)

I found some prolog lurking in the bowels of a Windows DLL recently.
Is this good or bad?

Tony.
--

genes <=> memes    proteins <=> algorithms    amino acids <=> machine code



Mon, 15 Jan 2001 03:00:00 GMT  
 Exception handling

  Richard> So we have a dilemma here:

  Richard> Horn 1: have exception handling at the price of often
  Richard> ending up in your states where some fraction of your
  Richard> variables are no longer defined

  Richard> Horn 2: always have variables you can trust, but find some
  Richard> other way of dealing with run-time problems.

  Richard> SML chose Horn 1; Clean chose Horn 2.  Haskell chose to
  Richard> have a form of exception handling within monads, but the
  Richard> price of that is that large chunks of your code have to
  Richard> change form, so that's Horn 3.  (This dilemma is a
  Richard> triceratops.)

Perhaps a word or two about how exceptions are handled in Erlang.

There is a catch/throw mechanism in Erlang which is sometimes useful,
but:

Normally, exceptions are handled by simply letting the process
crash. This obviously takes care of most variables, since the entire
process heap is discarded. Thus, processes are used in part to
encapsulate sequences for fault detection purposes.

The recovery principal here is to restart a process. This is done
by a "supervisor" process, which is linked to a number of workers
and knows how to restart them if it catches an exception.
The supervisor has a variety of restart strategies - one-for-one,
one-for-all, etc, allowing you to determine the scope of the
recovery action. It also has a restart frequency setting, which
means that if it performs a certain number of restarts within a
given time period, it will terminate itself, thus escalating the
recovery action.

On great advantage with this strategy is that programmers can
concentrate on writing clean code and not worry too much about
error detection. We call this "aggressive coding".

One trivial example: a function which reads a value from the database,
knowing that the operation must return exactly one object - or else
something is wrong - can write:

        [Object] = mnesia:read({Tab, Key})

instead of the more defensive

        case mnesia:read({Tab, Key}) of
            [] ->
                ... % this should never happen
            [Object] ->
                ... % proceed normally
        end.

What often happens in a case like this is that the process crashes
and performs some kind of reinitialization upon restart, which solves
the problem. This makes it remarkably easy to construct robust
systems.

/Uffe
--

Ericsson Telecom AB                          tfn: +46  8 719 81 95
Varuv?gen 9, ?lvsj?                          mob: +46 70 519 81 95
S-126 25 Stockholm, Sweden                   fax: +46  8 719 43 44



Tue, 16 Jan 2001 03:00:00 GMT  
 Exception handling

Quote:

>What does that mean in the case of binary operations like `+'?
>If both operands have exceptional values, which one gets passed?

I'm inclined to say, who cares? Whichever one gets evaluated first is good
enough. (If you parallelize the evaluations, of course, that becomes
problematic, but you can still just let the language choose which exception
to throw arbitrarily -- how often does it really matter which one gets
thrown?) The central issue is that the expression as a whole cannot be
evaluated, and therefore throws an appropriate exception. If there are two
(or more) reasons the expression cannot be evaluated, any one of them is
appropriate.

Craig



Tue, 16 Jan 2001 03:00:00 GMT  
 Exception handling

Quote:

> I found some prolog lurking in the bowels of a Windows DLL recently.
> Is this good or bad?

Probably good... what do you mean by lurking, there was prolog
source in the binary?

ABW



Tue, 16 Jan 2001 03:00:00 GMT  
 Exception handling

Quote:


>> What does that mean in the case of binary operations like `+'?
>> If both operands have exceptional values, which one gets passed?
...
>> A nice solution is to say that it returns a nondeterministic set.
>> (For details, see the Haskell mailing list archives.)

>Ah, but in my local slang, that's just buffling (= copping out, avoiding
>the Real Issue, going for the easy option) since one exception may well
>be more interesting than the other - if one side of the + threw a
>division by
>zero but the other side wanted to alert us of running out of disk space,
>we'd be
>more interested in the lack of disk space than some puny arithmetic
>error with no
>global consequence! Then again, maybe the division by zero is more
>important if the value
>being divided by is your bank balance...

I think that if an operation failed, you generally only need to know
one reason why it failed, not necessarily all.   If the cause is one
that you are capable of recovering from, then you can fix that problem
and then retry the operation.  If there are other problems still
remaining then the second attempt will also result in an exception,
and you can try and deal with that one when it arises.

So I don't think this is a significant issue in practice.
Note that other languages generally don't attempt to deal
with this one -- you just get whatever exception was thrown first.

Exceptions that occur while you are trying to clean up from the failure
of an operation (for example, in C++, when calling destructors during
the process of stack unwinding after an exception has been thrown)
is a different issue -- here you have multiple different operations
that have failed, so it may be important to record a cause for
each different failed operation.  But even that gets pretty tricky
and C++ does not attempt to handle it.

--

WWW: <http://www.cs.mu.oz.au/~fjh>  |  of excellence is a lethal habit"



Wed, 17 Jan 2001 03:00:00 GMT  
 Exception handling

Quote:
>In retrospect, I guess that means that something like Haskell's "Maybe"
>is the best way of handling exceptional situations right now, combined
>with the right utility functions to make it easy to use. That way we
>have to handle "exceptions" the very instant they emerge from the
>functions we call, the type system forces us to not forget about them,
>and so on. The Maybe type would need to be expanded so the failure case
>contained some useful information as to what failed, perhaps.

If by "right now" you mean "using the functionality that current
Haskell implementations provide", then I would agree.
However, this technique has a number of significant disadvantages.
The Haskell implementors are aware of these disadvantages and are
working on providing a better alternative.

The disadvantages are that
(1) it imposes unnecessary sequentiality, which
        - makes programs more difficult to reason about
        - overly constrains the optimizer
(2) it's inefficient (all that boxing and unboxing of `Maybe' values)
(3) it destroys laziness
(4) there's no support for this approach in the standard library
    (e.g. no version of Int '+' with overflow checks)

Quote:
>On a similar note, Dylan's restart exception things are quite nice and
>can fit into a pure functional model well if you have a form of dynamic
>binding.

I'm not familiar with Dylan's facilities for exception handling, but my
position on restartable exceptions in general is that this is an issue
for the design of the standard library rather than a language issue as
such, because restart exceptions can easily be implemented using
non-restartable exceptions.

I posted code for doing this using Haskell type classes to the Haskell
mailing list as part of the long discussion there.  (Type classes
are of course Haskell's form of dynamic binding.)

--

WWW: <http://www.cs.mu.oz.au/~fjh>  |  of excellence is a lethal habit"



Wed, 17 Jan 2001 03:00:00 GMT  
 Exception handling

Quote:


>>What does that mean in the case of binary operations like `+'?
>>If both operands have exceptional values, which one gets passed?

>I'm inclined to say, who cares? Whichever one gets evaluated first is good
>enough. (If you parallelize the evaluations, of course, that becomes
>problematic, but you can still just let the language choose which exception
>to throw arbitrarily -- how often does it really matter which one gets
>thrown?) The central issue is that the expression as a whole cannot be
>evaluated, and therefore throws an appropriate exception. If there are two
>(or more) reasons the expression cannot be evaluated, any one of them is
>appropriate.

I agree that this is the right solution.

However, modelling this in a referentially transparent way is
a little tricky.  In a pure functional language, functions must be
deterministic, but unless you nail down the order of evaluation
(which would be undesirable) the exception that is thrown is not
deterministic.

The solution to this dilemma is to say that the function returns
a set of exceptions, but to represent this set using an ADT
that only allows you to get at one element of it.    See [1].

This is explained in more detail in my mail to the Haskell mailing list.

[1]     John Hughes, John O'Donnell, ``Expressing and Reasoning About
        Non-deterministic Functional Programs,'' in Proc. of the 1989
        Glasgow Workshop on Functional Prog., pp. 308-28.

--

WWW: <http://www.cs.mu.oz.au/~fjh>  |  of excellence is a lethal habit"



Wed, 17 Jan 2001 03:00:00 GMT  
 Exception handling

Quote:

>> I found some prolog lurking in the bowels of a Windows DLL recently.
>> Is this good or bad?

>Probably good... what do you mean by lurking, there was prolog
>source in the binary?

Yes, using a sort of lispish syntax. The DLL in question is
\winnt\system32\netcfg.dll, IIRC.

Tony.
--

genes <=> memes    proteins <=> algorithms    amino acids <=> machine code



Fri, 19 Jan 2001 03:00:00 GMT  
 
 [ 24 post ]  Go to page: [1] [2]

 Relevant Pages 

1. Customizing exception handling in VW

2. C++ exception handling for Smalltalkers

3. Dolphin 98 exception handling problem

4. Exception Handling

5. Exception Handling

6. Exception Handling

7. Simple Exception handling questions

8. Exception Handling

9. exception handling in smalltalk /r

10. question about exception handling mechanism

11. Looking for exception handling teaching materials/examples

 

 
Powered by phpBB® Forum Software