Version 2 (modified by Austin_Hastings, 5 years ago)

--

This page documents my (Austin) response to a  request from Tene to complain  more about exceptions. This is like a "super TT#" in that maybe a bunch of change orders come out of it. (Maybe 0, too.) So other folks please, please chime in with stuff you know, or opine, about how exceptions ought to work.

--Austin


The point of exceptions is to enable a whole bunch of 'dynamic' (like scope) flow control.

'Typed' exceptions

The PCT stuff uses exceptions for things like return, break, and continue, as well as the conventional "a surprising result has occurred." There is a legitimate need for fast, static handling of different types of control transfers. Typed exceptions definitely are one answer to that need. It is conceivable that more types could be added.

'Classed' exceptions

Most programming languages support the idea of exception classes. Further, they support catching exceptions based on a class hierarchy: catch an exception of such-and-such class, or any subclass.

An exception class will likely extend a more basic exception class in the language. It is easily conceivable that exceptions may be implemented as roles, or using multiple inheritance.

So catching a classed exception either looks like "catch all exceptions, then filter by class, then rethrow the ones you don't like", or it looks like "register the classes you catch, some of which may be subclasses of each other, and have the dispatcher run through the 'isa' query to find a match".

'Resumable' exceptions

Perl is driving the idea of resumable exceptions, I think. (I remember advocating for it on p6l about 80 years ago.) This causes a problem in that a resumable exception doesn't necessarily roll up the stack. A non-resumable exception, which is what the industry is used to, does. So a resumable exception is kind of like the old ON ERR GOSUB directive - it calls a "sub-routine" (maybe) that might possibly return back to the site of the exception (or to some other continuation, blah blah). Or it might never be heard from again.

'finally'

There's no obvious way to implement finally, or something like it, if an outer exception handler is going to maybe resume the exception. If the exception is non-resumable, you can close those files. If the exception is definitely going to be resumed you should not close the files. But if you don't know, then what?

(The only answer to this that springs immediately to mind is to have a "last" exception handler that converts a resumable to a non-resumable, and throws that. I sure hope somebody smarter than me has thought about this...)

Requirements

The current typed system fails to handle this approach. It is unlikely that a single-type-per-exception approach will ever perform adequately in the presence of multiple inheritance and role mixins.

However, the current system is arguably fast - I have no data for this, but if it isn't fast then it has no redeeming value. A single numeric lookup deserves to be fast.

I think the priority has to be given to making 'simple things simple.' For exceptions, that means performance for the control flow exceptions, and reliability for die.

Performance, in this case, can mean whatever the internals guys want it to mean. But in general it should probably mean low cost and getting rid of nested interpreters.

Implementation of 'unusual exceptions' should be optimized not so much for speed, but for convenience. That is, getting rid of inferior runloops, or whatever other thing raises its head.

Requirements

  • The non-local flow, catchable exceptions, and fatal error mechanisms must be extensible - more different kinds of each may be added by user code.
  • Non-local control flow needs to be fast, for things like 'break', 'continue', 'return'
  • Handling of 'errors' needs to be reliable - no matter how bad the user code, a 'die' type error has to be delivered.
  • Catchable exceptions should be handled well, but may trade performance for implementation facility.

Design Issues

A cloud over much of this is coroutines. Coroutines, or some other form of random flow, have to support separate "stacks" for control transfer. That is, if a lexical block throws a "loop break" control exception, the right outer block has to receive it. I suspect this may have already been thought through - I certainly hope so.

That aside, control flow exceptions seem like they are really a different animal, piggy-backing on the exception mechanism. That's not wrong, per se, but it may be possible for control flow stuff to move to a different subsystem with better performance.

In particular, control flow is generally "symmetric" - that is, you don't generate a "next" without knowing there's a "for" somewhere to catch it. (In fact, I suspect most languages know where the for loop is, to boot. Does a control flow implicitly have a target?) In this regard, control flow is probably more like 'longjmp' than the rest of the exceptions mechanism - you know where you're going, and there's a good protocol defined between sender and receiver.

Presently, the throw internals are oriented towards a "nested call" instead of towards the "return the address of the next opcode". There are probably reasons for this, but are they good enough?

There has been some pressure to "make exception handlers be subs" (TT#1091), and the counter-pressure was the handling of control exceptions. If there is validity to the runloop problems being tied to continuations (vice subs), this would be a good opportunity for a split.