Formal comment #221 (defect) Recursive exception handling considered harmful Reported by: John Cowan Version: 5.92 Note: This comment is in part based on suggestions by Taylor Campbell, but he bears no responsibility for its content. In R5.92RS, a condition handler is invoked in a dynamic environment identical to that of the signaler, but with the condition handler stack popped by one frame. Failing to do so will cause the handler to be re-entered whenever an exception is raised within it, which is obviously disastrous. (Scheme48 behaves this way.) As a consequence, "raise" cannot tail-call the current handler, because it must pop and later restore the handler stack. Unfortunately, R5.92RS prescribes that a handler must reraise an exception in order to get it processed by the next handler in the dynamic chain. This loads the call stack with alternating calls to "raise" and to each handler in turn. This is a Bad Thing in an environment where there are many handlers and most only handle specific kinds of conditions. I propose, therefore, that the protocol for calling handlers be changed: 1) To decline to handle a condition and propagate it up to the next enclosing handler, a condition handler simply returns the condition that is to be propagated to the next enclosing handler. Only a constant amount of stack space, then, will delimit the handler and the signaler, regardless of how many handlers there are. No debugger traces will be cluttered by unnecessary frames caused by recursive signaling, except in the case where a procedure called by a raises a genuinely new exception. 2) In order to deal with continuable exceptions, "raise" is given a second argument, which is a continuation to be invoked if all handlers decline to handle the exception. The default value of this argument is system-dependent but does not return to the caller of "raise". The "raise-continuably" procedure is removed from R6RS. RESPONSE: The formal comment notes that the R5.92RS exception system requires exception handlers to decline to handle an exception by re-raising it via a call to `raise' (or to `raise-continuable'). Since raise itself then calls the next handler via a non-tail call, the usual exceptional case (which is to pass the exception to the outermost default handler) requires time and continuation space proportional to O(n), where n is the number of active handlers. The time required to get to the outermost handler would remain O(n) even with the proposed change. Indeed, the exception system is not designed to handle very large values of n, but these seem unlikely given the intended typical usage of the system, and precedents in other languages with similar systems. Even if n were large, the space required to represent the sequence of handlers would already be O(n), so the O(n) non-tail calls wouldn't change the asympotic space requirements of the program. Also, if several exception handlers decline, the actual chain of invocations might contain useful information, which might be worth preserving in a system where `raise' tail-calls the exception handler. Removing `raise-continuable' would would require us to reconsider the semantics of &no-infinities, &no-nans, &i/o-decoding, and &i/o-encoding exceptions (since the specifications of those exceptions/conditions rely upon continuable exceptions). Except for the space usage issue, the exception system can be used to implement a system like the one described, for example by using a special condition type to direct an outermost exception handler to tail-call a procedure included in the condition object. For these reasons, the formal comment's proposal will not be adopted.