Obtaining lazy evaluation with continuations in scheme

Obtaining lazy evaluation with continuations in scheme

Information Processing Letters 35 (1990) 93-97 North-Holland OBTAINING 29June1990 LAZY EVALUATION WITH CONTINUATIONS IN SCHEME Ching-lin WANG De...

417KB Sizes 0 Downloads 53 Views

Information Processing Letters 35 (1990) 93-97 North-Holland

OBTAINING

29June1990

LAZY EVALUATION

WITH CONTINUATIONS

IN SCHEME

Ching-lin WANG Department

of Computer Science, Tamkang

University

Tamsui, Taipei, Taiwan

Communicated by G.R. Andrews Received 28 August 1989 Revised 28 November 1989

The most common use of fist-class continuations in Scheme is to implement advanced control structures. In this paper we demonstrate that first-class continuations can also be used to implement lazy evaluation, and, consequently, to construct and manipulate streams and infinite data structures. In this regard we may give another interpretation to the term “data structure continuations” [16]. The paper is primarily concerned with continuations, not lazy evaluation. Our point is, in agreement with [lo], that there is still much room for developing progr amming techniques with continuations. The main contribution of the paper is to present a new idiom for the use of continuations in Scheme, which is rather different from the usual “escaping from (and returning to) recursions” idiom [13].

Keywords: Call-by-name, Scheme

call-by-need, continuations,

1. Introduction The most common use of first-class continuations in Scheme [3,14] is to implement advanced control structures such as, to name only a few, nonlocal exits [4,6], nonblind backtracking [5,8], coroutines [11,13], and timed pre-emption [9]. In this paper we demonstrate that first-class continuations can also be used to implement lazy evaluation, and, consequently, to construct and manipulate streams and infinite data structures. In this regard we may interpret the term “data structure continuations” [16, p. 1791 as data structures represented as continuations instead of as data structures representing continuations [16]. The paper is primarily concerned with continuations, not lazy evaluation. Our point is, in agreement with [lo, p. 5971, that there is still much room for developing programming techniques with continuations. The main contribution of the paper is to present a new idiom for the use of continuations in Scheme, which is rather different from the 0020-0190/90/$03.50

functional programming,

lazy evaluation, normal-order

evaluation,

usual “escaping from (and returning to) recursions” idiom [13]. Some familiarity with the usual implementations of delayed evaluation [1,2,12] and the Scheme language [1,4,6,13] is assumed. In Section 2 we use first-class continuations to implement normalorder evaluation (or call-by-name). Section 3 then extends the method of implement lazy evaluation (or call-by-need) in a number of ways. Section 4 presents a conclusion and a brief discussion on further work.

2. Implementing tinuations

normal-order evaluation with con-

The idea behind our implementations of delayed evaluation with continuations is quite simple: in delayed evaluation the evaluation of an expression is initially postponed, and will be COIZtimed when the value of the expression is demanded. That is to say, a delayed expression may

0 1990 - Elsevier Science Publishers B.V. (North-Holland)

93

Volume 35, Number 2

INFORMATION

PROCESSING

exp) ~1

(fre (d/cc

(lambda (FC) (let ([TC (call/cc (lambda(k)

(FC k)))])

(TC a~))))

29 June 1990

LETTERS

expression is thawed as the thawing-time continuation will take over the control. 2 To see this, suppose an expression was frozen within an arbitrary control context 3 &, then the delayed expression, being a continuation object, may be described as (lambda (v)

( WJ (let

(define thaw (lambda (delayed+bject) (=Wc (lambda (TC) (delayed+bject

TC)))))

Fig. 1. Implementation of normal-order evaluation with continuations.

be represented as a continuation object, which when invoked will continue to evaluate the expression as desired. In some implementations of Scheme [7,15], the nonstandard syntactic form freeze and the procedure thaw are used in combination to implement normal-order evaluation. We shall adopt the same names for these two operations. Figure 1 presents our implementation of normal-order evaluation. In Fig. 1, the variable FC, for Freezing-time Continuation, will be bound to the continuation current at the time the freeze expression is evaluated. The variable TC, for Thawing-time Continuation, will be bound to the continuation in effect when the thaw expression is evaluated. It is worth noting that in our implementation the representation of a delayed expression is actually quite similar to the usual one: in usual implementation, a delayed expression is represented as a call-by-name thunk, which is a functional object of no arguments; in ours, it is represented as a continuation, which is a functional object of one argument. In either case the expression is, of course, not evaluated at the delaying time. The correctness of this implementation is established on the ground that the freezing-time continuation will be discarded when the delayed

’ exp, = exp, means the pattern exp, is a syntactic sugar of the expansion exp, 94

([TC

4 (TCexd)))

Suppose furthermore that at some future time this delayed expression is thawed within a certain control context 5XB, then the evaluation will proceed as

a@11(TCew))) + (80 t&O:exp>)

(5@

(let ([TC

At this point the expression exp is evaluated as desired and its value is passed to 5XB. From then on, computation will proceed in a manner as directed by 5X6. Sooner or later, control will, in case of no errors, eventually return to the Scheme top level 3, thus making the freezing-time context 8 B ineffective. It should be stressed that the use of the let expression is essential to the implementation of the freeze operation. It can not be replaced by the expression ((call/cc

(lambda (k) (FCk)))

exp) because order of evaluation of operator and operand(s) in a function application is unspecified in Scheme [3]. Much more importantly, it serves to stack the sequence of thawing-time continuations in case thawing a frozen object causes the object to be recursively thawed. (Observe that the preced-

For the usual implementation using rhunks, the correctness can be proven by simply showing that (thaw(freeze exp)) = exp. This is because thuds are irrelevant to control contexts. The situation is different here for our implementation. The truth of the equation above does not immediately imply that of, for examples, (let([x(freeze exp)])(thaw x)) = exp, or (thaw x) = exp, given (define x(freeze exp)). This is true because every computation in Scheme is backed up by the top level continuation, i.e. the continuation representing the read-eval-print loop.

Volume

35, Number

INFORMATION

2

ing correctness arguments are still presence of recursive thawing.)

valid

PROCESSING

in the

LETTERS

29 June 1990

(delay exp) % (call/cc (lambda

3. Implementing lazy evaluation with continuations

(DC)

(let ([FC ‘any-non-continuation+bject]) (set! FC (begin0

The two operations required to implement lazy evaluation are normally named delay and force. Usually, there are essentially two distinct methods for implementing delay and force, called the closure mode and the cell mode in [2]. Correspondingly, we may also have two methods for implementing them using continuations. The closure mode is closely related to the usual implementation of normal-order evaluation: a delayed expression is represented as a call-by-need chunk which when invoked for the first time will memorize the value of the expression for further invocations. The corresponding mode using continuations is easily obtained by slightly modifying the implementation in Fig. 1, and shown in Fig. 2. There the variable DC serves initially as the delaying-time continuation and subsequently as the cache for the computed value. (This is justified by the fact that the delaying-time continuation is used only once at the delaying time.) The variable FC now denotes Forcing-time Continuation. Figure 2, as it stands, does nothing but recast the usual thunk-based implementation into the continuation-base one. In an attempt to fully

(delay exp) z (cwcc (lambda

(DC)

(let ([already-run?

#fl)

([FC (call/cc (lambda(k)

(let

(DC k)))])

(if (not already-run?) (begin (set! DC exp) (set! already-run?

(lambda(k)

(if (not (continuation?

(DC k))) FC))s

(set! DC exp)))) (PC DC)))) Fig. 3. Another

implementation

of the delay operation.

utilize the nature of continuations we present in Fig. 3 another way to implement the delay operation. There the variable FC serves both as the forcing-time continuation and as the status-checking flag. The use of the syntactic form begin0 4 is essential to this implementation. It serves not only to ensure the right evaluation order but also to stack the sequence of forcing-time continuations in the presence of recursive forcing. Notice that in case of recursive forcing the status-checking flag FC will remain bound to the arbitrary initial value in the winding phase of the recursion, and be rebound to forcing-time continuations in the unwinding phase of the recursion. That is to say, the status of the delayed object will not switch until the recursion reaches its end, and therefore the implementation is correct. Yet another implementation of the delay operation is shown in Fig. 4. This implementation is interesting in its own right in that it is, unlike the previous two, free of status-checking. The trick here is to set up the right “continuation point” inside the continuation object representing the delayed expression. At the delaying time the continuation point is set to the place where the expression is about to be evaluated. Upon invoking

#t)))

(PC DC)))))

(define force thaw) Fig. 2. Implementation (Related

(call/cc

of lazy evaluation with continuations. to the usual closure mode.)

4 (begin0 exp, . exp,) evaluates

the n expressions sequentially from left to right and returns the value of exp,. It is customized in some dialects of Scheme [7,15]. (See [5] for how to customize it.) 5 continuation? is not available in the current standard of Scheme [3], but is in some dialects [15]. The use of continuation? is not essential here, as the reader may check.

95

Volume 35, Number 2

INFORMATION PROCESSING LETTERS

(delay exp) E

mentation for normal-order evaluation. However, if we replace the last two expressions in Fig. 4 by

(=JJ/cc (lambda

29 June 1990

(DC)

(let ([FC (call/cc

(let ([continue ‘any-object]) (let ([FC (call/cc (lambda (k) (set! continue k)

k) (FC DC)))])

(FC DC))

(set! continue k)

we will get a variation for lazy evaluation. Moreover, substituting (FC exp) for the last (FC DC) in this variation will give a correct, albeit clumsy, implementation for normal-order evaluation. Another, more succinct, variation for lazy evaluation can be obtained by replacing the last two expressions in Fig. 4 by

(PC DC))))

((call/cc

(continue (d/cc

(lambda (k) (set! continue

(lambda (k) (DC k))))))l)

(set! DC exp) (set! FC (call/cc (lambda (k)

(PC DC))))) Fig. 4. Status-checking-free implementation of the delay operation.

the continuation object for the first time, the expression is evaluated and its value cached; besides, the continuation point is adjusted to the place where the cached value is about to be fetched. Subsequent invocations would then retrieve immediately the cached value as required. It should be noted that at the delaying time the continuation object bound to the variable continue may be described as

(lambda (k) (set! continue

k) FC))

DC) because order of evaluation is immaterial here. (Substituting exp for DC in this variation does not work for normal-order evaluation, as the reader may check.) Finally, the counterpart of the usual cell mode can be easily obtained in a like manner. We present it here in Fig. 5 merely for the sake of completeness. The delay operation may be implemented in another way by substituting (FC (cons #f k)) for (FC k) in Fig. 1. The resulting continuation object is different but the net effect is the same.

(lambda (v) (let ([FC v]) -body

of the let expression-))

Thus this implementation is correct even when recursive forcing is presented. After forcing a delayed object for the first time, the continuation object bound to the variable continue becomes (lambda (v) (set! FC v) (FC DC)) The fact that subsequent forcings can cause no recursive forcing justifies the use of the set! expression here. From the preceding description, it should be clear that substituting (FC exp) for the last (FC DC) in Fig. 4 does not make a correct imple96

(delay exp) z (cons #f (freeze exP))

(define force (lambda (delayed-object) (if (not (car delayed-object)) (J==dn (set-&!

delayed-object (thaw (cdr delayed-object)))

(set-car! delayed-bject

#t)))

(cdr delayed-object))) Fig. 5. Implementation of lazy evaluation with continuations. (Related to the usual cell mode.)

Volume 35. Number 2

INFORMATION PROCESSING LETTERS

4. Conclusion and further work We have presented a new idiom for the use of continuation in Scheme, and demonstrated that delayed evaluation, including normal-order evaluation and lazy evaluation, can be obtained using this idiom. As the cautious reader might have noticed, the essential use of this idiom as demonstrated in the paper is to represent nullary lambda functions as continuation functions, and applications to nullary lambda functions as applications to the corresponding continuation functions. There may have other applications of this idiom. One such possibility, as motivated by the use presented here, is to extend the method to represent every lambda function and application, of any arity, as continuation function and application. We are currently working towards this generalization. There are several interesting results that will be presented elsewhere.

Acknowledgment The author would like to express her deep gratitude to Prof. Louis R. Chow at Tamkang University for his encouragement throughout this work, and the anonymous referees for the provision of several up-to-date references [6,13] as well as the helpful comments on the presentation of the material.

References

29 June 1990

VI A. Bless, P. Hudak and J. Young, Code optimizations for lazy evaluation, Lisp and Symbolic Comput. 1 (1988) 147164. 131W. Clinger and J. Rues, eds., The revised 3 report on the algorithmic language Scheme, SZGPLAN Notices 21 (11) (1986) 37-79. 141R.K. Dybvig, The Scheme Programming Language (Prentice-Hall, EngIewood CIiffs, NJ, 1987). 151D.P. Friedman, CT. Haynes and E. Kohlbecker, Programming with continuations, in: P. Pepper, ed., Program Transformation and Programming Environments (Springer,

NJ, 1984) 263-274. 161D.P. Friedman, C.T. Haynes, E. Kohlbecker

and M. Wand, Programming Languages: Abstraction, Representation, and Implementation (MIT Press/McGraw-Hill, Cambridge, MA/New York, 1990). 171D.P. Friedman, C.T. Haynes, E. Kohlbecker and M. Wand, Scheme 84 Interim Reference Manual, Technique Report No. 153, Computer Science Dept., Indiana Univ., 1985. J. Logic Programming 181CT. Haynes, Logic continuations, 4 (1987) 157-176. 191C.T. Haynes and D.P. Friedman, Abstracting timed preemption with engines, Comput. Lang. 12 (2) (1987) 109121. 1101CT. Haynes and D.P. Friedman, Embedding continuations in procedural objects, ACM Trans. on Programming Languages and Systems 9 (4) (1987) 582-598. [III C.T. Haynes, D.P. Friedman and M. Wand, Obtaining coroutines with continuations, Comput. Lang. 11 (3/4) (1986) 143-153. Functional Programming: Application and 1121P. Henderson, Implementation (Prentice-Hall International, London, 1980). 1131G. Springer and D.P. Friedman, Scheme and the Art of Programming (MIT Press/McGraw-Hill, Cambridge, MA/New York, 1989). 1141G. Steele and G. Sussman, Scheme: an interpreter for extended lambda calculus, MIT Artificial Intelligence Memo 349, 1975. [15] Texas Instruments Inc., TZ Scheme Language Reference Manual, Rev. B, 1987. [16] M. Wand, Continuation-based program transformation strategies, J. ACM 27 (1) (1980) 164-180.

[l] H. Abelson and G.J. Sussman, Strucrure and Znterpretution of Computer Programs (MIT Press, Cambridge, MA, 1985).

97