|
|
# Exceptions and Control
|
|
|
|
|
|
Racket provides an especially rich set of control operations—not only
|
|
|
operations for raising and catching exceptions, but also operations for
|
|
|
grabbing and restoring portions of a computation.
|
|
|
|
|
|
1 Exceptions
|
|
|
|
|
|
2 Prompts and Aborts
|
|
|
|
|
|
3 Continuations
|
|
|
|
|
|
## 1. Exceptions
|
|
|
|
|
|
Whenever a run-time error occurs, an _exception_ is raised. Unless the
|
|
|
exception is caught, then it is handled by printing a message associated
|
|
|
with the exception, and then escaping from the computation.
|
|
|
|
|
|
```racket
|
|
|
> (/ 1 0)
|
|
|
/: division by zero
|
|
|
> (car 17)
|
|
|
car: contract violation
|
|
|
expected: pair?
|
|
|
given: 17
|
|
|
```
|
|
|
|
|
|
To catch an exception, use the `with-handlers` form:
|
|
|
|
|
|
```racket
|
|
|
(with-handlers ([predicate-expr handler-expr] ...)
|
|
|
body ...+)
|
|
|
```
|
|
|
|
|
|
Each `predicate-expr` in a handler determines a kind of exception that
|
|
|
is caught by the `with-handlers` form, and the value representing the
|
|
|
exception is passed to the handler procedure produced by `handler-expr`.
|
|
|
The result of the `handler-expr` is the result of the `with-handlers`
|
|
|
expression.
|
|
|
|
|
|
For example, a divide-by-zero error raises an instance of the
|
|
|
`exn:fail:contract:divide-by-zero` structure type:
|
|
|
|
|
|
```racket
|
|
|
> (with-handlers ([exn:fail:contract:divide-by-zero?
|
|
|
(lambda (exn) +inf.0)])
|
|
|
(/ 1 0))
|
|
|
+inf.0
|
|
|
> (with-handlers ([exn:fail:contract:divide-by-zero?
|
|
|
(lambda (exn) +inf.0)])
|
|
|
(car 17))
|
|
|
car: contract violation
|
|
|
expected: pair?
|
|
|
given: 17
|
|
|
```
|
|
|
|
|
|
The `error` function is one way to raise your own exception. It packages
|
|
|
an error message and other information into an `exn:fail` structure:
|
|
|
|
|
|
```racket
|
|
|
> (error "crash!")
|
|
|
crash!
|
|
|
> (with-handlers ([exn:fail? (lambda (exn) 'air-bag)])
|
|
|
(error "crash!"))
|
|
|
'air-bag
|
|
|
```
|
|
|
|
|
|
The `exn:fail:contract:divide-by-zero` and `exn:fail` structure types
|
|
|
are sub-types of the `exn` structure type. Exceptions raised by core
|
|
|
forms and functions always raise an instance of `exn` or one of its
|
|
|
sub-types, but an exception does not have to be represented by a
|
|
|
structure. The `raise` function lets you raise any value as an
|
|
|
exception:
|
|
|
|
|
|
```racket
|
|
|
> (raise 2)
|
|
|
uncaught exception: 2
|
|
|
> (with-handlers ([(lambda (v) (equal? v 2)) (lambda (v) 'two)])
|
|
|
(raise 2))
|
|
|
'two
|
|
|
> (with-handlers ([(lambda (v) (equal? v 2)) (lambda (v) 'two)])
|
|
|
(/ 1 0))
|
|
|
/: division by zero
|
|
|
```
|
|
|
|
|
|
Multiple `predicate-expr`s in a `with-handlers` form let you handle
|
|
|
different kinds of exceptions in different ways. The predicates are
|
|
|
tried in order, and if none of them match, then the exception is
|
|
|
propagated to enclosing contexts.
|
|
|
|
|
|
```racket
|
|
|
> (define (always-fail n)
|
|
|
(with-handlers ([even? (lambda (v) 'even)]
|
|
|
[positive? (lambda (v) 'positive)])
|
|
|
(raise n)))
|
|
|
> (always-fail 2)
|
|
|
'even
|
|
|
> (always-fail 3)
|
|
|
'positive
|
|
|
> (always-fail -3)
|
|
|
uncaught exception: -3
|
|
|
> (with-handlers ([negative? (lambda (v) 'negative)])
|
|
|
(always-fail -3))
|
|
|
'negative
|
|
|
```
|
|
|
|
|
|
Using `(lambda (v) #t)` as a predicate captures all exceptions, of
|
|
|
course:
|
|
|
|
|
|
```racket
|
|
|
> (with-handlers ([(lambda (v) #t) (lambda (v) 'oops)])
|
|
|
(car 17))
|
|
|
'oops
|
|
|
```
|
|
|
|
|
|
Capturing all exceptions is usually a bad idea, however. If the user
|
|
|
types Ctl-C in a terminal window or clicks the Stop button in DrRacket
|
|
|
to interrupt a computation, then normally the `exn:break` exception
|
|
|
should not be caught. To catch only exceptions that represent errors,
|
|
|
use `exn:fail?` as the predicate:
|
|
|
|
|
|
```racket
|
|
|
> (with-handlers ([exn:fail? (lambda (v) 'oops)])
|
|
|
(car 17))
|
|
|
'oops
|
|
|
> (with-handlers ([exn:fail? (lambda (v) 'oops)])
|
|
|
(break-thread (current-thread)) ; simulate Ctl-C
|
|
|
(car 17))
|
|
|
user break
|
|
|
```
|
|
|
|
|
|
## 2. Prompts and Aborts
|
|
|
|
|
|
When an exception is raised, control escapes out of an arbitrary deep
|
|
|
evaluation context to the point where the exception is caught—or all the
|
|
|
way out if the exception is never caught:
|
|
|
|
|
|
```racket
|
|
|
> (+ 1 (+ 1 (+ 1 (+ 1 (+ 1 (+ 1 (/ 1 0)))))))
|
|
|
/: division by zero
|
|
|
```
|
|
|
|
|
|
But if control escapes “all the way out,” why does the REPL keep going
|
|
|
after an error is printed? You might think that it’s because the REPL
|
|
|
wraps every interaction in a `with-handlers` form that catches all
|
|
|
exceptions, but that’s not quite the reason.
|
|
|
|
|
|
The actual reason is that the REPL wraps the interaction with a
|
|
|
_prompt_, which effectively marks the evaluation context with an escape
|
|
|
point. If an exception is not caught, then information about the
|
|
|
exception is printed, and then evaluation _aborts_ to the nearest
|
|
|
enclosing prompt. More precisely, each prompt has a _prompt tag_, and
|
|
|
there is a designated _default prompt tag_ that the uncaught-exception
|
|
|
handler uses to abort.
|
|
|
|
|
|
The `call-with-continuation-prompt` function installs a prompt with a
|
|
|
given prompt tag, and then it evaluates a given thunk under the prompt.
|
|
|
The `default-continuation-prompt-tag` function returns the default
|
|
|
prompt tag. The `abort-current-continuation` function escapes to the
|
|
|
nearest enclosing prompt that has a given prompt tag.
|
|
|
|
|
|
```racket
|
|
|
> (define (escape v)
|
|
|
(abort-current-continuation
|
|
|
(default-continuation-prompt-tag)
|
|
|
(lambda () v)))
|
|
|
> (+ 1 (+ 1 (+ 1 (+ 1 (+ 1 (+ 1 (escape 0)))))))
|
|
|
0
|
|
|
> (+ 1
|
|
|
(call-with-continuation-prompt
|
|
|
(lambda ()
|
|
|
(+ 1 (+ 1 (+ 1 (+ 1 (+ 1 (+ 1 (escape 0))))))))
|
|
|
(default-continuation-prompt-tag)))
|
|
|
1
|
|
|
```
|
|
|
|
|
|
In `escape` above, the value `v` is wrapped in a procedure that is
|
|
|
called after escaping to the enclosing prompt.
|
|
|
|
|
|
Prompts and aborts look very much like exception handling and raising.
|
|
|
Indeed, prompts and aborts are essentially a more primitive form of
|
|
|
exceptions, and `with-handlers` and `raise` are implemented in terms of
|
|
|
prompts and aborts. The power of the more primitive forms is related to
|
|
|
the word “continuation” in the operator names, as we discuss in the next
|
|
|
section.
|
|
|
|
|
|
## 3. Continuations
|
|
|
|
|
|
A _continuation_ is a value that encapsulates a piece of an expression’s
|
|
|
evaluation context. The `call-with-composable-continuation` function
|
|
|
captures the _current continuation_ starting outside the current
|
|
|
function call and running up to the nearest enclosing prompt. \(Keep in
|
|
|
mind that each REPL interaction is implicitly wrapped in a prompt.\)
|
|
|
|
|
|
For example, in
|
|
|
|
|
|
`(+` `1` `(+` `1` `(+` `1` `0)))`
|
|
|
|
|
|
at the point where `0` is evaluated, the expression context includes
|
|
|
three nested addition expressions. We can grab that context by changing
|
|
|
`0` to grab the continuation before returning 0:
|
|
|
|
|
|
```racket
|
|
|
> (define saved-k #f)
|
|
|
> (define (save-it!)
|
|
|
(call-with-composable-continuation
|
|
|
(lambda (k) ; k is the captured continuation
|
|
|
(set! saved-k k)
|
|
|
0)))
|
|
|
> (+ 1 (+ 1 (+ 1 (save-it!))))
|
|
|
3
|
|
|
```
|
|
|
|
|
|
The continuation saved in `save-k` encapsulates the program context `(+
|
|
|
1 (+ 1 (+ 1 ?)))`, where `?` represents a place to plug in a result
|
|
|
value—because that was the expression context when `save-it!` was
|
|
|
called. The continuation is encapsulated so that it behaves like the
|
|
|
function `(lambda (v) (+ 1 (+ 1 (+ 1 v))))`:
|
|
|
|
|
|
```racket
|
|
|
> (saved-k 0)
|
|
|
3
|
|
|
> (saved-k 10)
|
|
|
13
|
|
|
> (saved-k (saved-k 0))
|
|
|
6
|
|
|
```
|
|
|
|
|
|
The continuation captured by `call-with-composable-continuation` is
|
|
|
determined dynamically, not syntactically. For example, with
|
|
|
|
|
|
```racket
|
|
|
> (define (sum n)
|
|
|
(if (zero? n)
|
|
|
(save-it!)
|
|
|
(+ n (sum (sub1 n)))))
|
|
|
> (sum 5)
|
|
|
15
|
|
|
```
|
|
|
|
|
|
the continuation in `saved-k` becomes `(lambda (x) (+ 5 (+ 4 (+ 3 (+ 2
|
|
|
(+ 1 x))))))`:
|
|
|
|
|
|
```racket
|
|
|
> (saved-k 0)
|
|
|
15
|
|
|
> (saved-k 10)
|
|
|
25
|
|
|
```
|
|
|
|
|
|
A more traditional continuation operator in Racket \(or Scheme\) is
|
|
|
`call-with-current-continuation`, which is usually abbreviated
|
|
|
`call/cc`. It is like `call-with-composable-continuation`, but applying
|
|
|
the captured continuation first aborts \(to the current prompt\) before
|
|
|
restoring the saved continuation. In addition, Scheme systems
|
|
|
traditionally support a single prompt at the program start, instead of
|
|
|
allowing new prompts via `call-with-continuation-prompt`. Continuations
|
|
|
as in Racket are sometimes called _delimited continuations_, since a
|
|
|
program can introduce new delimiting prompts, and continuations as
|
|
|
captured by `call-with-composable-continuation` are sometimes called
|
|
|
_composable continuations_, because they do not have a built-in abort.
|
|
|
|
|
|
For an example of how continuations are useful, see \[missing\]. For
|
|
|
specific control operators that have more convenient names than the
|
|
|
primitives described here, see `racket/control`.
|