Previous chapter: #24: Divide by Zero Signals Error (Clarification)

Dylan Design Notes

Dylan Design Notes: #25: Exit Extent (Change)

#25: Exit Extent (Change)

Version 1, January 1994 Copyright (c) 1993-1994, Apple Computer
Interactions between bind-exit and unwind-protect are complicated, especially when a non-local exit is taken during the execution of an unwind-protect cleanup form. This design note replaces bind-exit and unwind-protect with a new block construct, and clarifies Dylan's behavior.
On page 71 and 72 of the Dylan manual, replace bind-exit and unwind-protect with the following block construct. Change all references to bind-exit and/or unwind-protect to refer to block as necessary throughout the manual.

block ([exit-var]) exprs  [cleanup cleanup-clauses]   [Special Form]

=> values
block executes the exprs in sequence, and then the optional cleanup-clauses. Normally, the value returned by block is the value of the last expr. If there are no exprs, #f is returned.

If exit-var is provided, exit-var is bound to an exit procedure during the execution of the exprs and cleanup-clauses. At any point in time before the last cleanup-clause returns, the exit procedure can be called. Calling the exit procedure has the effect of immediately terminating the execution of exprs. The exit procedure accepts any number of arguments. These arguments are used as the return values of block. Calling an exit procedure is known as performing a non-local exit.

Generally, the cleanup-clauses are guaranteed to be executed. Even if one of the exprs is terminated by a non-local exit out of the block, the cleanup-clauses are executed before the non-local exit can complete. For example, the following code fragment ensures that files are closed even in the case of an error causing a non-local exit:

block (return)
  open-files();
  ...
  if (error)
    return(#f);
  end if;
  ...
  result
cleanup
  close-files();
end block
However, if one of the cleanup-clauses is terminated by a non-local exit out of the block, any following cleanup-clauses within the same block are not executed.

Restrictions on the use of exit procedures

The exit procedure is a first-class object. Specifically, it can be passed as an argument to functions, stored in data structures, etc. Its use is not restricted to the lexical body of the establishing block (the block in which that exit procedure was established). However, invocation of the exit procedure is valid only during the execution of the establishing block. It is an error to invoke an exit procedure after its establishing block has returned, or after execution of the establishing block has been terminated by a non-local exit.

In the following example, the block establishes an exit procedure and binds bar to that exit procedure. The block returns an anonymous method containing bar, which is then bound to foo. Calling the foo method is an error because it is no longer valid to invoke bar after its establishing block returns.

? define foo
    block (bar)
       method (n) bar(n);
    end block;
  end foo.
? foo(5)
error or other undefined consequences
When an exit procedure is called, it initiates a non-local exit out of its establishing block. Before the non-local exit can complete, however, the cleanup clauses of intervening blocks (blocks that have been entered, but not exited, since the establishing block was entered) must be executed, beginning with the most recently entered intervening block. Once the cleanup clauses of an intervening block have been executed, it is an error to invoke the exit procedure established by that block. Finally, the cleanup clauses of the establishing block are executed, further invocation of the exit procedure becomes invalid, and the establishing block returns with the values that were passed to the exit procedure.

During the process of executing the cleanup clauses of the intervening blocks, any valid exit procedure may be invoked and may interrupt the current non-local exit.


Examples

The following expression is valid and returns 1. The call to two does not complete because it is interrupted by a non-local exit when the cleanup is executed.
block (one)
  block (two)
    two(2);
  cleanup
    one(1);
  end;
  3
end
The following expression is valid and returns 2. The exit procedure bound to exit is still valid when the cleanup is executed.
block (exit)
  exit(1);
cleanup
  exit(2);
end
The following expression returns 1 rather than 2. The second cleanup is never executed.
block (exit)
  3;
cleanup
  exit(1);
  exit(2);
end
The following expression returns 2. Nesting the blocks ensures that both cleanups will be executed.
block (exit)
  block ()
    3;
  cleanup
    exit(1);
  end;
cleanup
  exit(2);
end
The following expression is valid and returns 3. The call to one does not destroy information needed to invoke two.
block (one)
  block (two)
    one(1);
  cleanup
    two(2);
  end;
  3
end

Next chapter: #26: New Iteration Protocol (Change)