Lisp/Conditions

From Jonathan Gardner's Tech Wiki
Jump to: navigation, search

Overview

The Lisp Condition system allows:

  1. The signaling of conditions, which represent unusual or remarkable conditions in the code.
  2. The special handling of conditions by the caller, called condition handlers.
  3. The special retry code specified by the callee, called restarts.

Just using conditions and handlers, it is quite easy to duplicate the throw/try/catch exception system of many modern languages. However, the Lisp system also allows warning, serious conditions, and restarts, which are not found in most other languages.

Condition Types

These are the condition types: [1]

The main types are condition (base class for all conditions), simple-condition, serious-condition, error (derived from serious-condition, and warning.

Simple conditions are generated by signal when given a string for an argument.

Serious conditions arise when some major problem that prevents further calculation has arisen. Those that are of type error can be handled within Lisp. The others are very serious in nature and involve things like running out of memory or stack overflow.

Signalling a Condition

Typically, Lisp functions have no side effects when they signal condition, although this is only a convention.

signal

signal is used to signal any kind of condition. When signaling an error, error is preferred as a matter of convention. [2]

  • (signal datum &rest args)
    • If datum is a condition, then it is signaled. args should be empty. Ex: (signal my-condition-that-I-made)
    • If datum is a condition type, then it is instantiated with args and signaled.
    • If datum is a string, then a new condition of type simple-error is created with the string as the format and the args as its parameters.

error, cerror

The simplest way:

  • (error format-string &rest args): signals an error condition.
  • (error 'type args*)

This will signal a type error, build with the args specified.

cerror

If you may continue from the error condition, using some kind of reasonable alternative, then you may use cerror.

(cerror continue-format-string error-format-string &rest args)

This will signal the error described by error-format-string applied to args. The user will be asked if they want to continue with the message continue-format-string. If they affirm, then execution will continue. Otherwise, it will stop.

assert, check-type

warn

warn works like signal, except if the type of the warning is not of type warning, then a type-error is signaled instead. If it is a string with format args, then the simple-warning type is used.

While the warn is being signalled, a muffle-warning restart is created. This allows any handlers to signal that restart, which will stop the warn from being signalled.


Restarts

Restarts are functions that can be called before retrying a block of code. They can be invoked at any time, but commonly are invokved in response to a condition.

Restarts may be anonymous or given names. If they are given names, they can be easily found.

Restarts may specify a description of what they would do, and even specify how to collect parameters for the restart from the user.

Establishing Restarts

  • with-simple-restart: Provides for a single restart.
  • restart-case: Useful wrapper around restart-bind around a single expression.
  • restart-bind: Bind a class of restarts around a group of expressions.
  • with-condition-restarts: Not commonly used.

Finding and Manipulating Restarts

  • compute-restarts: Find all the applicable restarts and compute a list of them.
  • restart-name: Returns the name of the restart given
  • find-restart: Finds the restart with the given identifier.
  • invoke-restart: Invokes the given restart with the parameters.
  • invoke-restart-interactively: As invoke-restart, but prompts for the values to use according to the restart.

Restart Functions

These are functions commonly used to call a restart of the same name.

  • abort: Calls the abort restart, which should abort the operation altogether, going up to the highest level.
  • continue: Calls the continue restart, which should skip over the current operation and attempt to proceed.
  • store-value: Calls the store-value restart, which generally replaces the faulty value permanently.
  • use-value: Callst he use-value restart, which generally replaces the faulty value just for this operation.

The following restart is different from the rest because it is automatically created.

  • muffle-warning: Calls the muffle-warning restart, which is created by the warn function. This will cause the warning to be silenced.

When to Use Conditions

Signal an error when something unusual, that is, non-typical has occurred. For instance, a function is asked to do some task, and it can't complete that task for some reason, such as invalid instructions, or the system being in the wrong state.

Signal a serious condition when the lisp system is suffering a catastrophic failure.

Signal a warning or some other type of signal to allow callers to intervene in the operation of the routine under special circumstances. This could also be used for logging.


Alternatives to Conditions

In the past, and in C, return values don't really represent what the "result" of a function is. It represents whether the operation succeeded, and if not, how it failed. This leads to a bulky programming case, where every unusual circumstance has to be handled in some way immediately, rather than collecting all of the unusual circumstances in one place. This method is called returning a special value.

Another method of handling exceptional cases was to set a flag, which the caller should look at before continuing. This was even less friendly than returning a special value. This method is called setting a flag.

Another method is throwing an exception. This is when you exit the function through an unusual mechanism, and bubble up an exception to some handler.

Yet another method is logging an event, which spits out text to a log that can be monitored or read afterwards. This is useful for signalling those conditions that are noteworthy but not catastrophic.

Lisp's system is none of the above, although it can do all of them. Instead, it signals a condition, allowing handlers that may or may not be defined to handle it, if they desire to.

Definitions

  • Condition: An interesting situation in a program that has been detected and announced, or the objects used to represent them.
  • Error: A condition from which the program may not continue without some kind of intervention. The intervention may be by a user or by some automatic process.
  • Signal: To announce a condition.

Signalling an Error

Signalling a Condition

Assertions

Some other

Ignoring Errors

To ignore all errors:

(ignore-errors {form}*)

This will execute the code in form, but will not signal any errors signaled from them. The signalled error will be trapped and returned.

The result is the last form evaluated, if there were no errors. If there were, it returns NIL and the error as multiple values.

Handling Conditions

One way of reacting to a condition is to execute a block of code. This is what handler-bind does. Think of it kind of like try-catch and the conditions as exceptions.

(handler-bind ( {(typespec handler)}* ) {form}*)

Runs form, and if a condition is signalled, look through the typespecs for the type of the condition. If found, run the associated handler, which should be a function, passing the error in as a parameter.

Restarts

A restart is a bit of code that, if there is an error, can be invoked under different conditions. CLTL, Ref

The restart describes:

  1. What the restart report is. This is a message that the user will see that when selected will cause the restart to be invoked.
  2. An interactive input function, if necessary, to collect the appropriate parameters and return them as a list.
  3. A method that will perform the restart when it is invoked. This method may take parameters.

Anonymous restarts are simply restarts without a name. Named restarts can be invoked with invoke-restart.

  • restart-bind is the lowest-level form.
  • restart-case is a nice interface for restart-bind that is useful in many situations.
  • with-simple-restart provides for one restart, which is also very useful in many situations.
  • invoke-restart will invoke the named restart.

Types of Conditions

There are many different types of conditions, beyond simply error.

  • serious-condition reflects not just error (which is a subclass of serious-condition) but also severe errors that cannot be handled by Lisp, such as a stack overflow.

ignore-errors traps all conditions of type error.

Reference

  • Chapter 9: Conditions [3] in Common Lisp HyperSpec [4]
  • Chapter 29: Conditions [5] in Common Lisp, the Language, 2nd ed., Guy L. Steel [6]
  • Condition Handling in the Lisp Language Family, Kent M. Pitman, 2001 [7]
  • What Conditions (Exceptions) are Really About, Dan Weinreb, 2008-03-24 [8]