Here are some examples of the behavior of letcc
:
(letcc Xit (+ (Xit 15) 5)) = 15
(letcc Xit (lambda (x) (+ (Xit (lambda (x) 5)) x)))
cannot
be evaluated any further; (lambda ...)
is a value, and Xit
occurs free in it((letcc Xit (lambda (x) (+ (Xit (lambda (x) 5)) x))) 25)
= (letcc Xit2
([lambda (x) (+ ((lambda (v) (Xit2 [v 25]))
(lambda (x) 5))
x)]
25))
= (letcc Xit2
(+ ((lambda (v) (Xit2 [v 25])) (lambda (x) 5)) 25)
= (letcc Xit2
(+ (Xit2 [(lambda (x) 5) 25]) 25)
= (letcc Xit2 (+ (Xit2 5) 25)
= 5
In general, when a letcc
expression is evaluated, it turns its current
context (as in, ``complete textual context'') into a procedural object. This procedural
object is also known as a continuation object. When a continuation object
is applied, it forces the evaluator to remove the current evaluation context and
to re-create the context of the original letcc
expression, filled
with the value of its argument. Like procedures, continuation objects are first-class
values, which means they can be stored in data structures or tested by predicates.
In essence, letcc takes the current activation record and program counter at the point of invoking letcc and encapsulates them as a procedure waiting for a value to be returned. The body of the letcc is evaluated in the current environment extended by the binding of the letcc continuation name to the encapsulated procedure -- just as the body of a conventional let would be evaluated in an environment extending by the bindings specified in the definitions at the head of the let.
Now we understand the semantics of letcc
, but how do we use it
to write programs? We need to also understand its pragmatics. For instance,
letcc
can be used for exception handling.
Example: |
Let us write a procedure, lod ::= null | (cons [0-9] lod) Our procedure might look like this: (define PiHelp (lambda (l) (cond ((null? l) 1) (else (* (car l) (PiHelp (cdr l))))))) However, suppose it is possible that we can get an invalid digit (in the range
((bad? (car l)) (Xit #f)) where We use the following recipe for constructing such programs:
If we pass this new procedure exceptional data, we get (Pi '(1 2 b)) ==> (letcc Abort (PiHelp '(1 2 b) XXX)) ==> (letcc Abort (* 1 (*2 (PiHelp #f)))) ==> #f as desired. Exercises:
|
There are numerous control constructs that we can add to LC. Some of these are:
(raise M)
stops computation and returns the value of M
.
(raise M)
corresponds to an exceptional datum condition for the
meta-evaluator. Hence, it can be added to the evaluator by following the steps
above.raise
by
delimiting the extent to which it can escape. Such a construct is called an
abort delimiter, and is sometimes written as prompt
or #
.
(lambda (f G) (open-file f) (G f) (close-file f))
G
executes an raise
statement, then the file
will never be closed. This might be undesirable. To prevent this, we can instead
write(lambda (f G) (open-file f) (# (G f)) (close-file f))
#
can be added to the evaluator with the following code:((#? M) (letcc NewXit (MEval (#-body M) env NewXit)))
(# M H)
where H
is invoked only if M
aborts. (The code in
H
might typically be used to perform some clean-up action.) Additional
extensions are possible: we could have labeled exceptions, and we could also
have restartable exceptions (where raise
returns a value and
the continuation active at the time it was invoked).Here is the core of an interpreter that implements #
and raise
.
This version of #
takes a body and a handler, as outlined above.
The handler takes one argument, which is the value ``thrown'' by raise
.
(define MEval/ec (lambda (M env Exit) (cond ... ((raise? M) (Exit (MEval/ec (raise-expr M) env Exit))) ((#? M) ((letcc new-Exit (lambda () (MEval/ec (#-body M) env (lambda (raised-value) (new-Exit (lambda () (MApply/ec (MEval/ec (#-handler M) env Exit) raised-value Exit))))))))))))
(Note that all calls to the former MEval
will now have to call MEval/ec
instead, passing on the Exit
handler unchanged; only #
installs new handlers. MApply/ec
is similarly modified.)
At this point, we will conclude our study of meta-interpreters. We have thus far covered the following:
It would be worthwhile to note in passing some of the topics that we did not cover but which could be studied with the same methodology: