bevan@cs.man.ac.uk (Stephen J Bevan) (04/05/91)
[I originally posted this on the 29th of March, but I've a feeling it never made it off my local site. If it did, then I appologise for wasting time (and money) with a question nobody seems to be able to answer] What is the general style used to write exceptions and their handlers in Scheme? By exceptions I mean facilities similar to catch/throw in Lisp. The ways I can see of doing it are :- 1) Pass a success and fail continuation to every function. 2) Pass multiple continuations, one for each error to be handled. 3) Call a function that is assumed to be set by the user e.g. (define (foo x y z) ... (if (some-unexpected-error) (unexpected-error-exception args) ...)) where `unexpected-end-of-file-exception' as a default just aborts. It would be up the user to re-define this as appropriate. I guess the easiest way of doing this would be via fluid-let e.g. :- (fluid-let ((unexpected-error-exception (lambda x (do-something-sensible x)))) (foo an-arg another-arg yet-another-arg)) However, as fluid-let isn't part of the standard (as far as I'm aware), this solution isn't portable. So some questions :- * Are there better mechanisms that those above? * Any opinons as to which is the best? * Am I totally off course trying to use exceptions in Scheme? All answers greatefully received. Stephen J. Bevan bevan@cs.man.ac.uk
gyro@cymbal.reasoning.COM (Scott Layson Burson) (04/09/91)
Date: 5 Apr 91 08:03:48 GMT From: Stephen J Bevan <bevan@cs.man.ac.uk> What is the general style used to write exceptions and their handlers in Scheme? By exceptions I mean facilities similar to catch/throw in Lisp. The ways I can see of doing it are :- I don't know what most people do, but: 1) Pass a success and fail continuation to every function. 2) Pass multiple continuations, one for each error to be handled. I have been curious for some time about the style that would result from passing explicit exception continuations to every function (letting the default continuation serve for the normal case). One would think that this would clutter the code unacceptably, but I wonder if there might not be some way to mitigate or manage the clutter. So I have had it in the back of my mind for some time to attempt the experiment of writing a program in this style and seeing how it worked out. How might the clutter be dealt with? Well, consider that 1) it would consist of numerous additional arguments being passed to functions, and 2) Scheme has a very powerful mechanism, viz. lambda-abstraction, for encapsulating argument passing. So, for instance, imagine that CAR took a second argument which would be invoked if the object passed to CAR were not a cons. Then one could do something like (define (foo [...args...] fail) (let ((car (lambda (x) (car x fail)))) ... (car something) ...)) (One might well prefer giving the two-argument CAR a different name, e.g., CAR-GEN, where "gen" might mean "general" and/or "generator".) Would this really work in practice, for a program of substantial size and complexity? I don't know. But perhaps you can see why I'm curious about the possibility. But I'm not really recommending this, both because it's experimental and because to do it with reasonable efficiency would probably require access to the internals of one's Scheme implementation. 3) Call a function that is assumed to be set by the user e.g. (define (foo x y z) ... (if (some-unexpected-error) (unexpected-error-exception args) ...)) where `unexpected-end-of-file-exception' as a default just aborts. It would be up the user to re-define this as appropriate. I guess the easiest way of doing this would be via fluid-let e.g. :- (fluid-let ((unexpected-error-exception (lambda x (do-something-sensible x)))) (foo an-arg another-arg yet-another-arg)) However, as fluid-let isn't part of the standard (as far as I'm aware), this solution isn't portable. This approach is equivalent to the built-in exception systems of Zetalisp and (New) Common Lisp. If you're in a situation where you need to write a Scheme program of substantial size and production quality and are willing to limit yourself to Schemes that support FLUID-LET, I would recommend you do it this way. If you can't use FLUID-LET, however, I don't know what to suggest. -- Scott
pavel@parc.xerox.COM (Pavel Curtis) (04/09/91)
I believe that there is no standard way to define and use exceptions in portable Scheme programs, so you may as well use any of your proposed mechanisms. I don't think that you'll be particularly pleased with any of those solutions in the long run, however, on the ground of either performance, maintainability, or flexibility. We have a mechanism in SchemeXerox that I will (at some appropriate point) propose for inclusion in R5RS; it works as follows (this is quoted from the current SchemeXerox reference manual): 6.15. Signalling and handling errors An exceptional condition is a situation that technicaly falls within the contract of a system, but is not considered part of the system's normal functioning. For example, the procedure READ normally returns a datum parsed from a port, but also has defined behavior when the input is not syntactically correct (according to the Revised^4 Report, it ``signals an error''). This proposal presents a means for systems to report the occurrence of such exceptional conditions and to notice and recover from such reports. A signal is a Scheme value representing a particular exceptional condition; its precise semantics is a part of the contract of the signaller, the system reporting the condition. For modularity reasons, we expect that most signals will be instances of distinct, application-specific, opaque types; this allows handlers to recognize unambiguously the meaning of a particular signal, and signallers to control the capability to raise that signal. A handler is a procedure of one argument, a signal currently being raised. In broad terms, a handler has only two choices in dealing with each signal raised: 1. The handler may accept the signal, taking full responsibility for recovering from the exceptional condition. This is usually accomplished by invoking a continuation captured before the handler was enabled. 2. The handler may decline the signal, refusing to take final responsibility for recovering from the exceptional condition. This is accomplished simply by returning from the handler. Every SchemeXerox thread maintains a list of currently-active handlers. When a signal is raised, each handler on the list is applied to it in turn, most-recently-activated first, in the dynamic environment of the signaller. If all of the handlers decline the signal, then some unspecified action takes place; the debugger might be entered (if one is available), the thread might be frozen (awaiting a remote debugger), etc. We expect this to be well-defined for any particular set of circumstances, but unspecified in general. 6.15.1. Raising and handling signals The procedure WITH-HANDLER is the fundamental means for making a handler active over some dynamic extent. The new syntax HANDLER-CASE captures a particularly common idiom. The procedure RAISE is the fundamental means for raising signals. The procedure ERROR captures another common idiom. with-handler handler thunk [Procedure] The given handler is made active during the application of thunk to zero arguments. Whenever control leaves the thunk, either normally or via explicit invocation of a continuation, the handler is deactivated. Whenever control subsequently returns to the thunk, via invocation of a continuation created there, the handler is reactivated. handler-case expression (predicate (var) body) ... [Syntax] The given expression is evaluated and its results returned. If a signal is raised during the evaluation, each predicate in turn is evaluated (in the dynamic environment of the signaller) and applied to the signal. If the application returns true, then the corresponding body is evaluated in the dynamic environment of the handler-case form, with the corresponding var bound to the signal; in this case, the body's results are returned from the handler-case form. raise signal [Procedure] The given signal is raised as described above. This procedure does not return. error format-string arg1 arg2 ... [Procedure] A convenience for signalling conditions that are not expected to be handled. Equivalent to (raise (format #f format-string arg1 arg2 ...)) 6.15.2. Predefined signals Several types of conditions arise frequently in practice, so it is useful to standardize their corresponding signals. The following sections specify the procedures available for manipulating these predefined signals. ... several more sections detailing the signals raised by various standard procedures ... Pavel Curtis