Chapter 7
Conditions
Background
A long-standing problem of software engineering is the need to develop an organized way to deal with exceptions, situations that must be handled gracefully but that are not conceptually part of the normal operation of the program.
Of course it is possible to program exception handling without using special linguistic
features. For example, all functions could return an extra result that indicates whether
they succeeded or failed, functions could take an extra argument that they consult if an
exception occurs, or a designated exception-handling function could be called whenever a
problem arises. All of these approaches have been used in one real-life system or another,
but they are deficient in two ways. First, they are too informal and don't provide enough
structure to allow an organized, systematic approach to exception handling. Second, and more
importantly, the first two approaches do not provide textual separation between normal
code
and code for dealing with exceptions
; exception-related code is sprinkled
throughout the program. This leads to two problems: one is the well-known mistake of
forgetting to test error codes and thus failing to detect an exception (perhaps because the
programmer believed the error could never occur); the other is that program clarity is lost
because it isn't easy to think about the main flow of the program while temporarily ignoring
exceptions.
All exception systems involve the concept of signal
(sometimes
with a different name, such as raise
or throw
) and the concept of handle
(sometimes with a different name such as on-unit
or catch
). Most exception systems dynamically
match signalers with handlers, first invoking the most recently established matching
handler still active, and then, if that matching handler declines to handle the exception,
invoking the next most recent matching handler, and so on.
In addition, it is necessary to have a way to clean up when execution of a function is terminated by a nonlocal exit initiated either by the function itself or by something it explicitly or implicitly called.
Exception systems may be name-based or object-based, they may be exiting or calling, and they may or may not provide formal recovery mechanisms.
In a name-based exception system a program signals a name, and a
handler matches if it handles the same name or any.
The name is a constant in the
source text of the program, not the result of an expression.
In an object-based exception system a program signals an object, and a handler matches if it handles a type that object belongs to. Object-based exceptions are more powerful, because the object can communicate additional information from the signaler to the handler, because the object to be signaled can be chosen at run-time rather than signaling a fixed name, and because type inheritance in the handler matching adds abstraction and provides an organizing framework.
In an exiting exception system, all dynamic state between the handler and the signaler is unwound before the handler receives control, as if signaling were a nonlocal goto from the signaler to the handler.
In a calling exception system the signaler is still active when a handler receives control. Control can be returned to the signaler, as if signaling were a function call from the signaler to the handler.
Exiting exception systems are acceptable for errors. However, they do not work for an exception that is not an error and doesn't require an exit, either because there is a default way to handle it and recover or because it can safely be ignored by applications that don't care about it. Non-error exceptions are quite common in networked environments, in computers with gradually expiring resources (such as batteries), in complex user interfaces, and as one approach for reflecting hardware exceptions such as page protection violations or floating-point overflow to the application.
Most languages have not formalized how to recover from exceptions, leaving programmers to invent ad hoc mechanisms. However, a formal recovery mechanism is useful for several reasons: it ensures that recovery is implemented correctly; it allows options for recovery to be categorized just as exceptions are categorized; and it allows introspection on the options for recovery, for example, by a debugger.
The Dylan exception facility is object-based. It uses calling semantics but also provides exiting handlers. It provides formal recovery.