Next: , Previous: , Up: baselib   [Index]


4.14 Control features

This chapter describes various primitive procedures which control the flow of program execution in special ways.

Procedure: apply proc arg1rest-args

rest-args must be a list. proc should accept n arguments, where n is number of args plus the length of rest-args. The apply procedure calls proc with the elements of the list:

(append (list arg1 …) rest-args)

as the actual arguments.

If a call to apply occurs in a tail context, the call to proc is also in a tail context.

(apply + (list 3 4))                    ⇒  7

(define compose
  (lambda (f g)
    (lambda args
      (f (apply g args)))))

((compose sqrt *) 12 75)                ⇒  30
Procedure: call-with-current-continuation proc
Procedure: call/cc proc

proc should accept one argument. The procedure call/cc (which is the same as the procedure call-with-current-continuation) packages the current continuation as an “escape procedure” and passes it as an argument to proc.

The escape procedure is a Scheme procedure that, if it is later called, will abandon whatever continuation is in effect at that later time and will instead reinstate the continuation that was in effect when the escape procedure was created.

Calling the escape procedure may cause the invocation of before and after procedures installed using dynamic-wind.

The escape procedure accepts the same number of arguments as the continuation of the original call to call/cc.

The escape procedure that is passed to proc has unlimited extent just like any other procedure in Scheme. It may be stored in variables or data structures and may be called as many times as desired.

If a call to call/cc occurs in a tail context, the call to proc is also in a tail context.

The following examples show only some ways in which call/cc is used. If all real uses were as simple as these examples, there would be no need for a procedure with the power of call/cc.

(call-with-current-continuation
  (lambda (exit)
    (for-each (lambda (x)
                (if (negative? x)
                    (exit x)))
              '(54 0 37 -3 245 19))
    #t))
⇒  -3

(define list-length
  (lambda (obj)
    (call-with-current-continuation
      (lambda (return)
        (letrec ((r
                  (lambda (obj)
                    (cond ((null? obj) 0)
                          ((pair? obj)
                           (+ (r (cdr obj)) 1))
                          (else (return #f))))))
          (r obj))))))

(list-length '(1 2 3 4))                        ⇒  4
(list-length '(a b . c))                        ⇒ #f
(call-with-current-continuation procedure?)     ⇒ #t

NOTE Calling an escape procedure reenters the dynamic extent of the call to call/cc, and thus restores its dynamic environment.

Procedure: values obj

Delivers all of its arguments to its continuation. The values procedure might be defined as follows:

(define (values . things)
  (call-with-current-continuation
    (lambda (cont) (apply cont things))))

The continuations of all non–final expressions within a sequence of expressions, such as in lambda, begin, let, let*, letrec, letrec*, let-values, let*-values, case, and cond forms, usually take an arbitrary number of values.

Except for these and the continuations created by call-with-values, let-values, and let*-values, continuations implicitly accepting a single value, such as the continuations of ?operator and ?operands of procedure calls or the ?test expressions in conditionals, take exactly one value. The effect of passing an inappropriate number of values to such a continuation is undefined.

Procedure: call-with-values producer consumer

producer must be a procedure and should accept zero arguments. consumer must be a procedure and should accept as many values as producer returns. The call-with-values procedure calls producer with no arguments and a continuation that, when passed some values, calls the consumer procedure with those values as arguments. The continuation for the call to consumer is the continuation of the call to call-with-values.

(call-with-values
    (lambda () (values 4 5))
  (lambda (a b) b))
⇒  5

(call-with-values * -)
⇒  -1

If a call to call-with-values occurs in a tail context, the call to consumer is also in a tail context.

Implementation responsibilities: After producer returns, the implementation must check that consumer accepts as many values as consumer has returned.

Procedure: dynamic-wind before thunk after

before, thunk, and after must be procedures, and each should accept zero arguments. These procedures may return any number of values.

The dynamic-wind procedure calls thunk without arguments, returning the results of this call. Moreover, dynamic-wind calls before without arguments whenever the dynamic extent of the call to thunk is entered, and after without arguments whenever the dynamic extent of the call to thunk is exited. Thus, in the absence of calls to escape procedures created by call/cc, dynamic-wind calls before, thunk, and after, in that order.

While the calls to before and after are not considered to be within the dynamic extent of the call to thunk, calls to the before and after procedures of any other calls to dynamic-wind that occur within the dynamic extent of the call to thunk are considered to be within the dynamic extent of the call to thunk.

More precisely, an escape procedure transfers control out of the dynamic extent of a set of zero or more active dynamic-wind calls x … and transfer control into the dynamic extent of a set of zero or more active dynamic-wind calls y …. It leaves the dynamic extent of the most recent x and calls without arguments the corresponding after procedure. If the after procedure returns, the escape procedure proceeds to the next most recent x, and so on. Once each x has been handled in this manner, the escape procedure calls without arguments the before procedure corresponding to the least recent y. If the before procedure returns, the escape procedure reenters the dynamic extent of the least recent y and proceeds with the next least recent y, and so on. Once each y has been handled in this manner, control is transferred to the continuation packaged in the escape procedure.

Implementation responsibilities: The implementation must check the restrictions on thunk and after only if they are actually called.

(let ((path '())
      (c #f))
  (let ((add (lambda (s)
               (set! path (cons s path)))))
    (dynamic-wind
      (lambda () (add 'connect))
      (lambda ()
        (add (call-with-current-continuation
               (lambda (c0)
                 (set! c c0)
                 'talk1))))
      (lambda () (add 'disconnect)))
    (if (< (length path) 4)
        (c 'talk2)
        (reverse path))))
⇒ (connect talk1 disconnect connect talk2 disconnect)

(let ((n 0))
  (call-with-current-continuation
    (lambda (k)
      (dynamic-wind
        (lambda ()
          (set! n (+ n 1))
          (k))
        (lambda ()
          (set! n (+ n 2)))
        (lambda ()
          (set! n (+ n 4))))))
  n)
⇒ 1

(let ((n 0))
  (call-with-current-continuation
    (lambda (k)
      (dynamic-wind
        values
        (lambda ()
          (dynamic-wind
            values
            (lambda ()
              (set! n (+ n 1))
              (k))
            (lambda ()
              (set! n (+ n 2))
              (k))))
        (lambda ()
          (set! n (+ n 4))))))
  n)
⇒ 7

NOTE Entering a dynamic extent restores its dynamic environment.


Next: , Previous: , Up: baselib   [Index]