[Next] [Previous] [Up] [Top] [Contents] [Index]

7 Conditions

Exception Handling

A set of classes, functions, and associated conventions extend the underlying condition handling capabilities to provide a complete exception handling facility.

The classes are described in "Conditions" on page 232, and the functions are described in "Signaling Conditions" on page 344.

Stack Model

Condition handlers are installed dynamically, with more recent handlers shadowing previously installed handlers. In addition, exception handling often involves the use of non-local exits. For these reasons it is useful to describe the behavior of the exception system using the following terms from the stack model of function calling.

Figure 7-1

The handler in Figure 7-1 may either return normally, in which case execution resumes as the call to signal returns normally, or the handler may make a non-local exit, such as calling the exit function from a dynamically active block statement.

Recovery and Exits

There are two ways to handle an exception: by recovery, or by exit. Recovery involves making some repair to the program state and leaving control in the signaling unit. Exit involves transfering control outside of the signaling unit through the use of a non-local exit.

The simplest way to handle an exception is to exit the signaling unit by taking a non-local exit to a target established in the outside stack. The exception clause of the block statement provides a convenient mechanism for accomplishing this.

A less common handling style is to exit the signaling unit by taking a non-local exit to a target established in the middle stack, thus leaving the handler in force.

Instead of exiting, a handler can recover by returning control to the signaling unit. This can be done either by returning values that the signaling unit will understand or by taking a non-local exit to a target established in the inside stack.

The following examples show three ways of handling a copy-protection violation while copying a series of files. Note that the signaling code does not need to know how the condition will be handled. The only changes are in the code which handles the condition.

// Assume there is a class for file-system errors.
// We are interested in a special kind of file-system error
// that occurs when attempting to copy a copy-protected file,
// so we define a new class to indicate such errors.
define class <copy-protection-violation> (<file-system-error>)
  slot file, init-keyword: file:;    // Store the file name
end class;
// Define a function to copy a single file. This
// function signals a <copy-protection-violation> if
// the file is copy-protected.
define method copy-file (source, destination)
  if ( copy-protected?(source) )
    signal(make(<copy-protection-violation>, file: source));
  else
    // copy normally
    notify-user("Copying %s to %s.", source, destination);
  end if;
end method;
// The following function copies a sequence of files.
// If one of the files is copy-protected, the user is
// notified, and the remaining files are copied.
define method backup-all-possible (volume, archive)
  let handler <copy-protection-violation>
       = method (condition, next)
           // The handler just notifies the user and continues
           notify-user("The file %s could not be copied.",
                       condition.file);
         end method;
  // start copying files, with the handler in effect
  for (each-file in volume)
    copy-file(each-file, archive)
  end for;
end method;
// The following function stops copying as soon as it
// hits a copy-protected file
define method backup-exit (volume, archive)
  // set up a block so we can do a non-local exit
  block (exit)
   let handler <copy-protection-violation>
       = method (condition, next)
           // Notify the user and abort the backup
           notify-user(
    "Backup interrupted: the file %s could not be copied.",
                       condition.file);
           exit(#f);
         end method;
  // start copying files, with the handler in effect
    for (each-file in volume)
      copy-file(each-file, archive)
    end for;
  end block;
end method;
// The following function uses the convenient exception clause of
// the block statement to achieve essentially the same effect as
// as backup-exit.
define method backup-block (volume, archive)
  // get ready to do backups   
  block ()
    // start copying files   
    for (each-file in volume)
      copy-file(each-file, archive)
    end for;
  exception (condition :: <copy-protection-violation>)
    notify-user(
   "Backup interrupted: the file %s could not be copied.",
                condition.file);
  end block;
end method;

Restarts

Recovering or exiting can be accomplished directly, or a more formal mechanism called restarting can be used. Using restarts provides more assurance that the handler and the signaling unit agree on the meaning of what they are doing and provides some isolation of the handler from names and data representations internal to the signaling unit.

A handler restarts by signaling a restart. All restarts are instances of <restart>. Any values needed for recovery are passed in the restart (that is, in initialization arguments that the restart remembers, typically in slots). The restart is handled by a restart handler which either returns or takes a non-local exit. If the restart handler returns some values, signal returns those values and the handler that called signal also returns them. The call to signal from the signaling unit that signaled the original condition returns the same values, and the signaling unit recovers as directed by those values.

Recovery Protocols

For every condition class there should be a recovery protocol that defines the meaning of handling by returning, the meaning of the values returned, and which restart handlers are supposed to be established by the signaling unit. The recovery protocol tells the handler what to expect from the signaler. For many condition classes, this is the empty protocol: handling by returning isn't allowed, and no particular restart handlers are provided. In this case only handling by exiting is possible. (Exiting might be accomplished by signaling a restart whose handler was established in the outside or middle stack and does a non-local exit back to where it was established, or by an ordinary non-local exit.) The recovery protocol for a subclass should be compatible with the recovery protocol of a superclass. That is, a handler that applies a class's recovery protocol should operate correctly when the condition is an instance of some subclass of that class.

An example recovery protocol for a hypothetical <unbound-slot> condition could include the following:


Dylan Reference Manual - 17 OCT 1995
[Next] [Previous] [Up] [Top] [Contents] [Index]

Generated with Harlequin WebMaker