ISSUE: Block Dynamic Environment REVISION HISTORY: 22 Sep 94, Rob MacLachlan [fix example, add macro example, clarify current practice] 13 Sep 94, Rob MacLachlan [exit scope covers exceptions] 12 Sep 94, Rob MacLachlan STATUS: open RELATED ISSUES: At Most One Cleanup CATEGORY: change/clarification PROBLEM DESCRIPTION: The "block" statement is the primary statement which manipulates the dynamic environment (remember "let handler"), yet its effect on the dynamic environment is not clear. This is at least partly because there is no concept of a single dynamic environment; instead there is a piecemeal discussion of legal and illegal situations. Some statements are inconsistent with a unified dynamic environment (one-stack model.) In particular, p36 in the DIRM says: If one of the cleanup-clauses is terminated by a non-local exit out of the block, any following cleanup clauses in the same block are not executed. Most importantly, the semantics are simply very confusing, since it seems that each "cleanup" or "exception" clause is executed in a different dynamic environment (modified by the preceding or following clauses.) PROPOSAL: Change/clarify that a block establishes up to three distinguishable bindings in the dynamic environment: Cleanup clause Exit procedure Exception clauses These portions of the block statement are executed with the specified bindings in effect: Body: Exit procedure/Exception clauses/Cleanup clause Cleanup clause: Exit procedure/Exception clauses Exception clauses: Exit procedure All exception clauses are executed in the same dynamic environment (none of the handlers established in the block are visible during the execution of the exception clauses.) This can be thought of as parallel instantiation of the handler bindings. Although this scoping rule is probably sufficient to define the intended semantics, some passages in the DIRM implicitly contradict this interpretation and need to be changed or qualified to make the intent clear. In the paragraph on pp36-37: When an exit procedure is invoked [...] Finally, the cleanup clauses of the establishing block are executed, further invocation of the exit procedure becomes invalid [...] Add a new paragraph: Note that a block statement may also be the target of a non-local exit due to the execution of a handler clause. Before the exception clause is executed, intervening cleanup clauses are executed as described above (including any clause for the establishing block.) The exit procedure may be invoked during execution of exception clauses, in which case the argument values are immediately returned from the block (the cleanup clause already having been executed.) On page 160: Note that when the expressions in the Exception-Body are executed, the HANDLER established BY THAT EXCEPTION CLAUSE is no longer active. Amend to: Note that when the expressions in the Exception-Body are executed, all handlers established by the block are no longer active. There are also various mentions of multiple cleanup clauses that should be eliminated on pages 35-37 and 160. RATIONALE: Although the intent isn't started in the DIRM, it is fairly clear (from the provision for multiple cleanup clauses) that the dynamic environments of the cleanup/exception clauses were intended to interleave. If (as proposed) all cleanup clauses are executed in the same dynamic environment, then there would be no point in having more than one, so this proposal should be adopted along with "At Most One Cleanup". Note that with default parallel binding, sequential exception bindings can be easily (and readably) introduced by wrapping additional enclosing "block" statements, whereas if sequential were the the default, getting parallel semantics requires complex code using "let handler" and additional exit procedures. The ordering of the of the three dynamic bindings is intended to preserve as many as possible of the necessarily arbitrary rules mentioned in the DIRM: -- The exit surrounds all of the body since that seems to be strongly implied by binding the exit procedure at the start of the statement. -- The cleanup is inside the exception clauses since that seems most natural with the documented terminating semantics. The cleanup is part of terminating the body, not part of handling the exception. It isn't completely clear that the abstract concept of "dynamic environment" should be introduced in user documentation, but some sort of "stack + search" model should be documented, since it already implicit and is partially described in the conceptual matter at the front of the conditions chapter. See especially the discussion of "outside stack/middle stack/inside stack" on page 150. EXAMPLES: Consider: block (done) stuff; exception () HANDLER1; exception () HANDLER2; end; If handler binding were done sequentially, then later exception clauses handle conditions signalled by earlier ones, so if HANDLER1 signals , then HANDLER2 would be invoked. In this proposal, all exception clauses are executed in the same environment, so some outer handler for would be run. It appears that the current Apple implementation does in fact implement parallel semantics for contiguous Exception clauses, in which case this is a clarification not a change. With this proposal, the above block could be expanded into: block (done) block (internal-cond1) block (internal-cond2) let handler = method (cond, next) internal-cond2(cond); end method; let handler = method (cond, next) internal-cond1(cond); end method; done(stuff); end block; // internal-cond1 done(HANDLER1); end block; // internal-cond2 done(HANDLER2); end block; // done Note reversal of the "let handler" bindings to ensure that the first exception clause shadows the second. Note also that this example is only correct when the main body and the handler clauses only return one value. In general, the implementation must arrange arrange to return all values, which could be done by: let (#rest vals) = begin ...body... end; apply(exit-proc, vals); Here is a new version of an example block macro skeleton which can replace the one in the macro proposal: define macro block { block (?name) ?block-body end } => { with-exit(method(?name) block() ?block-body end end) } { block () ?body:main-body cleanup ?body:cleanup-body end } => { with-cleanup(method () ?main-body end, method () ?cleanup-body end) } { block () ?body end } => { ?body } { block () ?exception-stuff end } => { with-handlers(?exception-stuff) } exception-stuff: { ... exception (?excp, #rest ?opt, #key ?test, ?description) ?body } => { ... , list(helper1(?excp) ?body end, helper2(?excp) end, ?opt) } { ?body } => { method() ?body end } { ?body:main-body cleanup ?body:cleanup-body } => { method() block() ?body:main-body cleanup ?body:cleanup-body end; end;} end macro; // extract the variable name bound to the condition around the handler define macro helper1 { helper1 (?excp) ?body end } => { method(?excp) ?body end } excp: { ?expr } => { condition :: ?expr } { ?name :: ?expr } => { ?name :: ?expr} end macro; // extract the condition type so with-handlers doesn't have to call // function-specializers on the handler thunk to get it define macro helper2 { helper2(?excp) end } => { ?excp } excp: { ?expr } => { ?expr } { ?name :: ?expr } => { ?expr } end macro; Note that this macro expands into hypothetical primitives With-Exit, With-Cleanup and With-Handlers, and does not produce an expansion resembling the one above. The idea is that With-Handlers' first argument is a thunk of the protected part of the block, and the remaining arguments are handler descriptions, which are lists of handler thunk, condition type, and keyword options. BENEFITS: Replaces an unbounded number of dynamic environments composed of two different kinds of bindings with at most three environments. This eliminates unnecessary complexity without loss of power. Several internal contradictions in the DIRM are also fixed.