Exceptions and conditions

Scheme allows programs to deal with exceptional situations using two cooperating facilities: The exception system for raising and handling exceptional situations, and the condition system for describing these situations.

The exception system allows the program, when it detects an exceptional situation, to pass control to an exception handler, and for dynamically establishing such exception handlers. Exception handlers are always invoked with an object describing the exceptional situation. Scheme’s condition system provides a standardized taxonomy of such descriptive objects, as well as a facility for extending the taxonomy.

6.1  Exceptions

This section describes Scheme’s exception-handling and exception-raising constructs provided by the (r6rs exceptions)library.

Note:   This specification follows SRFI 34 [6].

Exception handlers are one-argument procedures that determine the action the program takes when an exceptional situation is signalled. The system implicitly maintains a current exception handler.

The program raises an exception by invoking the current exception handler, passing to it an object encapsulating information about the exception. Any procedure accepting one argument may serve as an exception handler and any object may be used to represent an exception.

The system maintains the current exception handler as part of the dynamic environment of the program, the context for dynamic-wind. The dynamic environment can be thought of as that part of a continuation that does not specify the destination of any returned values. It includes the dynamic-wind context and the current exception handler.

When a program begins its execution, the current exception handler is expected to handle all &serious conditions by interrupting execution, reporting that an exception has been raised, and displaying information about the condition object that was provided. The handler may then exit, or may provide a choice of other options. Moreover, the exception handler is expected to return when passed any other non-&serious condition. Interpretation of these expectations necessarily depends upon the nature of the system in which programs are executed, but the intent is that users perceive the raising of an exception as a controlled escape from the situation that raised the exception, not as a crash.

procedure:  (with-exception-handler handler thunk) 

Handler must be a procedure that accepts one argument. The with-exception-handler procedure returns the result(s) of invoking thunk. Handler is installed as the current exception handler for the dynamic extent (as determined by dynamic-wind) of the invocation of thunk.

syntax:  (guard (<variable> <clause1> <clause2> ...) <body>) 

Syntax: Each <clause> must have the same form as a cond clause. (See report section on “Derived conditionals”.)

Semantics: Evaluating a guard form evaluates <body> with an exception handler that binds the raised object to <variable> and within the scope of that binding evaluates the clauses as if they were the clauses of a cond expression. That implicit cond expression is evaluated with the continuation and dynamic environment of the guard expression. If every <clause>’s <test> evaluates to false and there is no else clause, then raise is re-invoked on the raised object within the dynamic environment of the original call to raise except that the current exception handler is that of the guard expression.

procedure:  (raise obj) 

Raises a non-continuable exception by invoking the current exception handler on obj. The handler is called with a continuation whose dynamic environment is that of the call to raise, except that the current exception handler is the one that was in place when the handler being called was installed. The continuation of the handler raises a non-continuable exception with condition type &non-continuable.

procedure:  (raise-continuable obj) 

Raises a continuable exception by invoking the current exception handler on obj. The handler is called with a continuation that is equivalent to the continuation of the call to raise-continuable with these two exceptions: (1) the current exception handler is the one that was in place when the handler being called was installed, and (2) if the handler being called returns, then it will again become the current exception handler. If the handler returns, the values it returns become the values returned by the call to raise-continuable.

(guard (con
         ((error? con)
          (if (message-condition? con)
              (display (condition-message con))
              (display "an error has occurred")
              ’error))
         ((violation? con)
          (if (message-condition? con)
              (display (condition-message con))
              (display "the program has a bug"))
          ’violation))
  (raise
    (condition
      (&error)
      (&message (message "I am an error")))))
    prints: I am an error
           ===⇒ error

(guard (con
         ((error? con)
          (if (message-condition? con)
              (display (condition-message con))
              (display "an error has occurred")
              ’error)))
  (raise
    (condition
      (&violation)
      (&message (message "I am an error")))))
          ===⇒  &violation exception

(guard (con
         ((error? con)
          (display "error opening file")
          #f))
  (call-with-input-file "foo.scm" read))
    prints: error opening file
           ===⇒ #f

(with-exception-handler
  (lambda (con)
    (cond
      ((not (warning? con))
       (raise con))
      ((message-condition? con)
       (display (condition-message con)))
      (else
       (display "a warning has been issued")))
    42)
  (lambda ()
    (+ (raise-continuable
         (condition
           (&warning)
           (&message
             (message "should be a number"))))
       23)))
    prints: should be a number
           ===⇒ 65

6.2  Conditions

The section describes Scheme (r6rs conditions)library for creating and inspecting condition types and values. A condition value encapsulates information about an exceptional situation, or exception. Scheme also defines a number of basic condition types.

Note:   This specification is similar to, but not identical with SRFI 35 [7].

Scheme conditions provides two mechanisms to enable communication about exceptional situation: subtyping among condition types allows handling code to determine the general nature of an exception even though it does not anticipate its exact nature, and compound conditions allow an exceptional situation to be described in multiple ways.

Rationale:   Conditions are values that communicate information about exceptional situations between parts of a program. Code that detects an exception may be in a different part of the program than the code that handles it. In fact, the former may have been written independently from the latter. Consequently, to facilitate effective handling of exceptions, conditions must communicate as much information as possible as accurately as possible, and still allow effective handling by code that did not precisely anticipate the nature of the exception that occurred.

6.2.1  Condition objects

Conditions are objects with named fields. Each condition belongs to one or more condition types. Each condition type specifies a set of field names. A condition belonging to a condition type includes a value for each of the type’s field names. These values can be extracted from the condition by using the appropriate field name.

The condition system distinguishes between simple conditionsand compound conditions. A compound condition consists of an ordered set of simple conditions. Thus, every condition can be viewed as an ordered set of simple component conditions: If it is simple, the set consists of the condition itself; if it is compound, it consists of the simple conditions that compose it.

There is a tree of condition types with the distinguished &condition as its root. All other condition types have a parent condition type.

procedure:  (make-condition-type id parent field-names) 

Returns a new condition type. Id must be a symbol that serves as a symbolic name for the condition type. Parent must itself be a condition type. Field-names must be a list of symbols. It identifies the fields of the conditions associated with the condition type.

Field-names must be disjoint from the field names of parent and its ancestors.

procedure:  (condition-type? thing) 

Returns #t if thing is a condition type, and #f otherwise.

procedure:  (make-condition type alist) 

Returns a simple condition value belonging to condition type type. Alist must be an association list mapping field names to arbitrary values. There must be a pair in the association list for each field of type and its direct and indirect supertypes. The make-condition procedure returns the condition value, which fields and values as indicated by alist.

procedure:  (condition? obj) 

Returns #t if obj is a condition object, and #f otherwise.

procedure:  (condition-has-type? condition condition-type) 

The condition-has-type? procedure tests if condition condition belongs to condition type condition-type. It returns #t if any of condition’s types includes condition-type, either directly or as an ancestor, and #f otherwise.

procedure:  (condition->list condition) 

Returns a list of the component conditions of condition.

procedure:  (condition-ref condition type field-name) 

Condition must be a condition, type a condition type, and field-name a symbol naming a field of type or its direct or indirect supertypes. Moreover, condition must be a simple condition of type type, or a compound condition containing a simple condition of type type. The condition-ref procedure returns the value of the field named by field-name in the first component condition of condition that has type type.

procedure:  (make-compound-condition condition1 ...) 

Returns a compound condition consisting of the component conditions of condition1, ..., in that order.

syntax:  (define-condition-type <condition-type> 

<supertype>
<predicate>
<field-spec1...)

Syntax: <Condition-type>, <supertypes>, and <predicate> must all be identifiers. Each <field-spec> must be of the form

(<field> <accessor>) where both <field> and <accessor> must be identifiers.

Semantics: The define-condition-type form expands into a set of definitions:

syntax:  (condition <type-field-binding1> ...) 

Returns a condition value. Each <type-field-binding> must be of the form

(<condition-type> <field-binding1...) Each <field-binding> must be of the form (<field> <expression>)   where <field> is a field identifier from the definition of <condition-type>.

The condition returned by condition is created by a call of the form

(make-compound-condition
  (make-condition <condition-type>
                  (list (cons ’<field-name> <expression>)
                        ...))
  ...)
with the condition types retaining their order from the condition form.

condition type:  &condition 

This is the root of the entire condition type hierarchy. It has no fields.

(define-condition-type &c &condition
  c?
  (x c-x))

(define-condition-type &c1 &c
  c1?
  (a c1-a))

(define-condition-type &c2 &c
  c2?
  (b c2-b))

(define v1
  (make-condition &c1
    (list (cons ’x "V1")
          (cons ’a "a1"))))

(c? v1)                ===⇒ #t
(c1? v1)               ===⇒ #t
(c2? v1)               ===⇒ #f
(c-x v1)               ===⇒ "V1"
(c1-a v1)              ===⇒ "a1"

(define v2 (condition (&c2
                        (x "V2")
                        (b "b2"))))

(c? v2)                ===⇒ #t
(c1? v2)               ===⇒ #f
(c2? v2)               ===⇒ #t
(c-x v2)               ===⇒ "V2"
(c2-b v2)              ===⇒ "b2"

(define v3 (condition (&c1
                       (x "V3/1")
                       (a "a3"))
                      (&c2
                       (x "V3/2")
                       (b "b3"))))

(c? v3)                ===⇒ #t
(c1? v3)               ===⇒ #t
(c2? v3)               ===⇒ #t
(c-x v3)               ===⇒ "V3/1"
(c1-a v3)              ===⇒ "a3"
(c2-b v3)              ===⇒ "b3"

(define v4 (make-compound-condition v1 v2))

(c? v4)                ===⇒ #t
(c1? v4)               ===⇒ #t
(c2? v4)               ===⇒ #t
(c-x v4)               ===⇒ "V1"
(c1-a v4)              ===⇒ "a1"
(c2-b v4)              ===⇒ "b2"

(define v5 (make-compound-condition v2 v3))

(c? v5)                ===⇒ #t
(c1? v5)               ===⇒ #t
(c2? v5)               ===⇒ #t
(c-x v5)               ===⇒ "V2"
(c1-a v5)              ===⇒ "a3"
(c2-b v5)              ===⇒ "b2"

6.3  Standard condition types

condition type:  &message 
procedure:  (message-condition? obj) 
procedure:  (condition-message condition) 

This condition type could be defined by

(define-condition-type &message &condition
  message-condition?
  (message condition-message))
It carries a message further describing the nature of the condition to humans.

condition type:  &warning 
procedure:  (warning? obj) 

This condition type could be defined by

(define-condition-type &warning &condition
  warning?)
This type describes conditions that do not, in principle, prohibit immediate continued execution of the program, but may interfere with the program’s execution later.

condition type:  &serious 
procedure:  (serious-condition? obj) 

This condition type could be defined by

(define-condition-type &serious &condition
  serious-condition?)

This type describes conditions serious enough that they cannot safely be ignored. This condition type is primarily intended as a supertype of other condition types.

condition type:  &error 
procedure:  (error? obj) 

This condition type could be defined by

(define-condition-type &error &serious
  error?)
This type describes errors, typically caused by something that has gone wrong in the interaction of the program with the external world or the user.

condition type:  &violation 
procedure:  (violation? obj) 

This condition type could be defined by

(define-condition-type &violation &serious
  violation?)
This type describes violations of the language standard or a library standard, typically caused by a programming error.

condition type:  &non-continuable 
procedure:  (non-continuable? obj) 

This condition type could be defined by

(define-condition-type &non-continuable &violation
  non-continuable?)
This type denotes that an exception handler invoked via raise has returned.

condition type:  &implementation-restriction 
procedure:  (implementation-restriction? obj) 

This condition type could be defined by

(define-condition-type &implementation-restriction
    &violation
  implementation-restriction?)
This type describes a violation of an implementation restriction allowed by the specification, such as the absence of representations for NaNs and infinities. (See section 9.2.)

condition type:  &lexical 
procedure:  (lexical-violation? obj) 

This condition type could be defined by

(define-condition-type &lexical &violation
  lexical-violation?)
This type describes syntax violations at the level of the read syntax.

condition type:  &syntax 
procedure:  (syntax-violation? obj) 

This condition type could be defined by

(define-condition-type &syntax &violation
  syntax-violation?
  (form syntax-violation-form)
  (subform syntax-violation-subform))

This type describes syntax violations. The form field contains the erroneous syntax object or a datum representing the code of the erroneous form. The subform field may contain an optional syntax object or datum within the erroneous form that more precisely locates the violation. It can be #f to indicate the absence of more precise information.

condition type:  &undefined 
procedure:  (undefined-violation? obj) 

This condition type could be defined by

(define-condition-type &undefined &violation
  undefined-violation?)
This type describes unbound identifiers in the program.

condition type:  &assertion 
procedure:  (assertion-violation? obj) 

This condition type could be defined by

(define-condition-type &assertion &violation
  assertion-violation?)
This type describes an invalid call to a procedure, either passing an invalid number of arguments, or passing an argument of the wrong type.

condition type:  &irritants 
procedure:  (irritants-condition? obj) 
procedure:  (condition-irritants condition) 

This condition type could be defined by

(define-condition-type &irritants &condition
  irritants-condition?
  (irritants condition-irritants))
The irritants field should contain a list of objects. This condition provides additional information about a condition, typically the argument list of a procedure that detected an exception. Conditions of this type are created by the error and assertion-violation procedures of report section on “Errors and violations”.

condition type:  &who 
procedure:  (who-condition? obj) 
procedure:  (condition-who condition) 

This condition type could be defined by

(define-condition-type &who &condition
  who-condition?
  (who condition-who))
The who field should contain a symbol or string identifying the entity reporting the exception. Conditions of this type are created by the error and assertion-violation procedures (report section on “Errors and violations”), and the syntax-violation procedure (section on “Syntax violations”).