Mercurial > dylan
comparison categorical/imperative.org @ 2:b4de894a1e2e
initial import
author | Robert McIntyre <rlm@mit.edu> |
---|---|
date | Fri, 28 Oct 2011 00:03:05 -0700 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
1:8d8278e09888 | 2:b4de894a1e2e |
---|---|
1 #+TITLE: LAMBDA: The Ultimate Imperative | |
2 #+AUTHOR: Guy Lewis Steele Jr. and Gerald Jay Sussman | |
3 | |
4 * Abstract | |
5 | |
6 We demonstrate how to model the following common programming constructs in | |
7 terms of an applicative order language similar to LISP: | |
8 - Simple Recursion | |
9 - Iteration | |
10 - Compound Statements and Expressions | |
11 - GO TO and Assignment | |
12 - Continuation—Passing | |
13 - Escape Expressions | |
14 - Fluid Variables | |
15 - Call by Name, Call by Need, and Call by Reference | |
16 The models require only (possibly self-referent) lambda application, | |
17 conditionals, and (rarely) assignment. No complex data structures such as | |
18 stacks are used. The models are transparent, involving only local syntactic | |
19 transformations. | |
20 Some of these models, such as those for GO TO and assignment, are already well | |
21 known, and appear in the work of Landin, Reynolds, and others. The models for | |
22 escape expressions, fluid variables, and call by need with side effects are | |
23 new. This paper is partly tutorial in intent, gathering all the models | |
24 together for purposes of context. | |
25 This report describes research done at the Artificial Intelligence Laboratory | |
26 of the Massachusetts Institute of Teehnology. Support for the laboratory's | |
27 artificial intelligence research is provided in part by the Advanced Research | |
28 Projects Agency of the Department of Defense under Office of Naval Research | |
29 contract N000l4-75-C-0643. | |
30 | |
31 * Introduction | |
32 | |
33 We catalogue a number of common programming constructs. For each | |
34 construct we examine "typical" usage in well-known programming languages, and | |
35 then capture the essence of the semantics of the construct in terms of a | |
36 common meta-language. | |
37 The lambda calculus {Note Alonzowins} is often used as such a meta- | |
38 language. Lambda calculus offers clean semantics, but it is clumsy because it | |
39 was designed to be a minimal language rather than a convenient one. All | |
40 lambda calculus "functions" must take exactly one "argument"; the only "data | |
41 type" is lambda expressions; and the only "primitive operation‘ is variable‘ | |
42 substitution. While its utter simplicity makes lambda calculus ideal for | |
43 logicians, it is too primitive for use by programmers. The meta-language we | |
44 use is a programming language called SCHEME {Note Schemepaper) which is based | |
45 on lambda calculus. | |
46 SCHEME is a dialect of LISP. [McCarthy 62] It is an expression- | |
47 oriented, applicative order, interpreter-based language which allows one to | |
48 manipulate programs as data. It differs from most current dialects of LISP in | |
49 that it closes all lambda expressions in the environment of their definition | |
50 or declaration, rather than in the execution environment. {Note Closures} | |
51 This preserves the substitution semantics of lambda calculus, and has the | |
52 consequence that all variables are lexically scoped, as in ALGOL. [Naur 63] | |
53 Another difference is that SCHEME is implemented in such a way that tail- | |
54 recursions execute without net growth of the interpreter stack. {Note | |
55 Schemenote} We have chosen to use LISP syntax rather than, say, ALGOL syntax | |
56 because we want to treat programs as data for the purpose of describing | |
57 transformations on the code. LISP supplies names for the parts of an | |
58 executable expression and standard operators for constructing expressions and | |
59 extracting their components. The use of LISP syntax makes the structure of | |
60 such expressions manifest. We use ALGOL as an expository language, because it | |
61 is familiar to many people, but ALGOL is not sufficiently powerful to express | |
62 the necessary concepts; in particular, it does not allow functions to return | |
63 functions as values. we are thus forced to use a dialect of LISP in many | |
64 cases. | |
65 We will consider various complex programming language constructs and | |
66 show how to model them in terms of only a few simple ones. As far as possible | |
67 we will use only three control constructs from SCHEME: LAMBDA expressions, as | |
68 in LISP, which are Just functions with lexically scoped free variables; | |
69 LABELS, which allows declaration of mutually recursive procedures (Note | |
70 Labelsdef}; and IF, a primitive conditional expression. For more complex | |
71 modelling we will introduce an assignment primitive (ASET).i We will freely | |
72 assume the existence of other comon primitives, such as arithmetic functions. | |
73 The constructs we will examine are divided into four broad classes. | |
74 The first is sfinph?Lo0pl; this contains simple recursions and iterations, and | |
75 an introduction to the notion of continuations. The second is hmponflivo | |
76 Connrucls; this includes compound statements, G0 T0, and simple variable | |
77 assignments. The third is continuations, which encompasses the distinction between statements and expressions, escape operators (such as Landin's J- | |
78 operator [Landin 65] and Reynold's escape expression [Reynolds 72]). and fluid | |
79 (dynamically bound) variables. The fourth is Parameter Passing Mechanisms, such | |
80 as ALGOL call—by-name and FORTRAN call-by-location. | |
81 Some of the models presented here are already well-known, particularly | |
82 those for G0 T0 and assignment. [McCarthy 60] [Landin 65] [Reynolds 72] | |
83 Those for escape operators, fluid variables, and call-by-need with side | |
84 effects are new. | |
85 ** Simple Loops | |
86 By /simple loops/ we mean constructs which enable programs to execute the | |
87 same piece of code repeatedly in a controlled manner. Variables may be made | |
88 to take on different values during each repetition, and the number of | |
89 repetitions may depend on data given to the program. | |
90 *** Simple Recursion | |
91 One of the easiest ways to produce a looping control structure is to | |
92 use a recursive function, one which calls itself to perform a subcomputation. | |
93 For example, the familiar factorial function may be written recursively in | |
94 ALGOL: ' | |
95 #+begin_src algol | |
96 integer procedure fact(n) value n: integer n: | |
97 fact := if n=0 then 1 else n*fact(n-1); | |
98 #+end_src | |
99 | |
100 The invocation =fact(n)= computes the product of the integers from 1 to n using | |
101 the identity n!=n(n-1)! (n>0). If $n$ is zero, 1 is returned; otherwise =fact=: | |
102 calls itself recursively to compute $(n-1)!$ , then multiplies the result by $n$ | |
103 and returns it. | |
104 | |
105 This same function may be written in Clojure as follows: | |
106 #+begin_src clojure | |
107 (ns categorical.imperative) | |
108 (defn fact[n] | |
109 (if (= n 0) 1 (* n (fact (dec n)))) | |
110 ) | |
111 #+end_src | |
112 | |
113 #+results: | |
114 : #'categorical.imperative/fact | |
115 | |
116 Clojure does not require an assignment to the ``variable'' fan: to return a value | |
117 as ALGOL does. The IF primitive is the ALGOL if-then-else rendered in LISP | |
118 syntax. Note that the arithmetic primitives are prefix operators in SCHEME. | |
119 | |
120 *** Iteration | |
121 There are many other ways to compute factorial. One important way is | |
122 through the use of /iteration/. | |
123 A comon iterative construct is the DO loop. The most general form we | |
124 have seen in any programming language is the MacLISP DO [Moon 74]. It permits | |
125 the simultaneous initialization of any number of control variables and the | |
126 simultaneous stepping of these variables by arbitrary functions at each | |
127 iteration step. The loop is terminated by an arbitrary predicate, and an | |
128 arbitrary value may be returned. The DO loop may have a body, a series of | |
129 expressions executed for effect on each iteration. A version of the MacLISP | |
130 DO construct has been adopted in SCHEME. | |
131 | |
132 The general form of a SCHEME DO is: | |
133 #+begin_src nothing | |
134 (DO ((<var1> (init1> <stepl>) | |
135 ((var2> <init2> <step2>) | |
136 (<varn> <intn> (stepn>)) | |
137 (<pred> (value>) | |
138 (optional body>) | |
139 #+end_src | |
140 The semantics of this are that the variables are bound and initialized to the | |
141 values of the <initi> expressions, which must all be evaluated in the | |
142 environment outside the D0; then the predicate <pred> is evaluated in the new | |
143 environment, and if TRUE, the (value) is evaluated and returned. Otherwise | |
144 the (optional body) is evaluated, then each of the steppers <stepi> is | |
145 evaluated in the current environment, all the variables made to have the | |
146 results as their values, the predicate evaluated again, and so on. | |
147 Using D0 loops in both ALGOL and SCHEME, we may express FACT by means | |
148 of iteration. | |
149 #+begin_src algol | |
150 integer procedure fact(n); value n; integer n: | |
151 begin | |
152 integer m, ans; | |
153 ans := 1; | |
154 for m := n step -l until 0 do ans := m*ans; | |
155 fact := ans; | |
156 end; | |
157 #+end_src | |
158 | |
159 #+begin_src clojure | |
160 (in-ns 'categorical.imperative) | |
161 (defn fact-do[n] | |
162 ) | |
163 #+end_src | |
164 | |
165 Note that the SCHEME D0 loop in FACT has no body -- the stepping functions do | |
166 all the work. The ALGOL DO loop has an assignment in its body; because an | |
167 ALGOL DO loop can step only one variable, we need the assignment to step the | |
168 the variable "manually'. | |
169 In reality the SCHEME DO construct is not a primitive; it is a macro | |
170 which expands into a function which performs the iteration by tail—recursion. | |
171 Consider the following definition of FACT in SCHEME. Although it appears to | |
172 be recursive, since it "calls itself", it is entirely equivalent to the DO | |
173 loop given above, for it is the code that the DO macro expands into! It | |
174 captures the essence of our intuitive notion of iteration, because execution | |
175 of this program will not produce internal structures (e.g. stacks or variable | |
176 bindings) which increase in size with the number of iteration steps. | |
177 | |
178 #+begin_src clojure | |
179 (in-ns 'categorical.imperative) | |
180 (defn fact-do-expand[n] | |
181 (let [fact1 | |
182 (fn[m ans] | |
183 (if (zero? m) ans | |
184 (recur (dec m) (* m ans))))] | |
185 (fact1 n 1))) | |
186 #+end_src | |
187 | |
188 From this we can infer a general way to express iterations in SCHEME in | |
189 a manner isomorphic to the HacLISP D0. The expansion of the general D0 loop | |
190 #+begin_src nothing | |
191 (DO ((<varl> <init1> <step1>) | |
192 (<var2> (init2) <step2>) | |
193 ... | |
194 (<varn> <initn> <stepn>)) | |
195 (<pred> <va1ue>) | |
196 <body>) | |
197 #+end_src | |
198 is this: | |
199 #+begin_src nothing | |
200 (let [doloop | |
201 (fn [dummy <var1> <var2> ... <varn>) | |
202 (if <pred> <value> | |
203 (recur <body> <step1> <step2> ... <stepn>)))] | |
204 | |
205 (doloop nil <init1> <init2> ... <initn>)) | |
206 #+end_src | |
207 The identifiers =doloop= and =dummy= are chosen so as not to conflict with any | |
208 other identifiers in the program. | |
209 Note that, unlike most implementations of D0, there are no side effects | |
210 in the steppings of the iteration variables. D0 loops are usually modelled | |
211 using assignment statements. For example: | |
212 #+begin_src nothing | |
213 for r :1 0 step b until c do <statement>; | |
214 #+end_src | |
215 | |
216 can be modelled as follows: [Naur 63] | |
217 #+begin_src nothing | |
218 begin | |
219 x := a; | |
220 L: if (x-c)*sign(n) > 0 then go to Endloop; | |
221 <statement>; | |
222 x := x+b; | |
223 go to L: | |
224 Endloop: | |
225 end; | |
226 #+end_src | |
227 Later we will see how such assignment statements can in general be | |
228 modelled without using side effects. | |
229 | |
230 * Imperative Programming | |
231 Lambda calculus (and related languages, such as ``pure LISP'') is often | |
232 used for modelling the applicative constructs of programming languages. | |
233 However, they are generally thought of as inappropriate for modelling | |
234 imperative constructs. In this section we show how imperative constructs may | |
235 be modelled by applicative SCHEME constructs. | |
236 ** Compound Statements | |
237 The simplest kind of imperative construct is the statement sequencer, | |
238 for example the compound statement in ALGOL: | |
239 #+begin_src algol | |
240 begin | |
241 S1; | |
242 S2; | |
243 end | |
244 #+end_src | |
245 | |
246 This construct has two interesting properties: | |
247 - (1) It performs statement S1 before S2, and so may be used for | |
248 sequencing. | |
249 - (2) S1 is useful only for its side effects. (In ALGOL, S2 must also | |
250 be a statement, and so is also useful only for side effects, but | |
251 other languages have compound expressions containing a statement | |
252 followed by an expression.) | |
253 | |
254 The ALGOL compound statement may actually contain any number of statements, | |
255 but such statements can be expressed as a series of nested two-statement | |
256 compounds. That is: | |
257 #+begin_src algol | |
258 begin | |
259 S1; | |
260 S2; | |
261 ... | |
262 Sn-1; | |
263 Sn; | |
264 end | |
265 #+end_src | |
266 is equivalent to: | |
267 | |
268 #+begin_src algol | |
269 begin | |
270 S1; | |
271 begin | |
272 S2; | |
273 begin | |
274 ... | |
275 | |
276 begin | |
277 Sn-1; | |
278 Sn; | |
279 end; | |
280 end; | |
281 end: | |
282 end | |
283 #+end_src | |
284 | |
285 It is not immediately apparent that this sequencing can be expressed in a | |
286 purely applicative language. We can, however, take advantage of the implicit | |
287 sequencing of applicative order evaluation. Thus, for example, we may write a | |
288 two-statement sequence as follows: | |
289 | |
290 #+begin_src clojure | |
291 ((fn[dummy] S2) S1) | |
292 #+end_src | |
293 | |
294 where =dummy= is an identifier not used in S2. From this it is | |
295 manifest that the value of S1 is ignored, and so is useful only for | |
296 side effects. (Note that we did not claim that S1 is expressed in a | |
297 purely applicative language, but only that the sequencing can be so | |
298 expressed.) From now on we will use the form =(BLOCK S1 S2)= as an | |
299 abbreviation for this expression, and =(BLOCK S1 S2...Sn-1 Sn)= as an | |
300 abbreviation for | |
301 | |
302 #+begin_src algol | |
303 (BLOCK S1 (BLOCK S2 (BLOCK ... (BLOCK Sn-1 Sn)...))) | |
304 #+end_src | |
305 | |
306 ** 2.2. The G0 TO Statement | |
307 | |
308 A more general imperative structure is the compound statement with | |
309 labels and G0 T0s. Consider the following code fragment due to | |
310 Jacopini, taken from Knuth: [Knuth 74] | |
311 #+begin_src algol | |
312 begin | |
313 L1: if B1 then go to L2; S1; | |
314 if B2 then go to L2; S2; | |
315 go to L1; | |
316 L2: S3; | |
317 #+end_src | |
318 | |
319 It is perhaps surprising that this piece of code can be /syntactically/ | |
320 transformed into a purely applicative style. For example, in SCHEME we | |
321 could write: | |
322 | |
323 #+begin_src clojure | |
324 (in-ns 'categorical.imperative) | |
325 (let | |
326 [L1 (fn[] | |
327 (if B1 (L2) (do S1 | |
328 (if B2 (L2) (do S2 (L1)))))) | |
329 L2 (fn[] S3)] | |
330 (L1)) | |
331 #+end_src | |
332 | |
333 As with the D0 loop, this transformation depends critically on SCHEME's | |
334 treatment of tail-recursion and on lexical scoping of variables. The labels | |
335 are names of functions of no arguments. In order to ‘go to" the labeled code, | |
336 we merely call the function named by that label. | |
337 | |
338 ** 2.3. Simple Assignment | |
339 Of course, all this sequencing of statements is useless unless the | |
340 statements have side effects. An important side effect is assignment. For | |
341 example, one often uses assignment to place intermediate results in a named | |
342 location (i.e. a variable) so that they may be used more than once later | |
343 without recomputing them: | |
344 | |
345 #+begin_src algol | |
346 begin | |
347 real a2, sqrtdisc; | |
348 a2 := 2*a; | |
349 sqrtdisc := sqrt(b^2 - 4*a*c); | |
350 root1 := (- b + sqrtdisc) / a2; | |
351 root2 := (- b - sqrtdisc) / a2; | |
352 print(root1); | |
353 print(root2); | |
354 print(root1 + root2); | |
355 end | |
356 #+end_src | |
357 | |
358 It is well known that such naming of intermediate results may be accomplished | |
359 by calling a function and binding the formal parameter variables to the | |
360 results: | |
361 | |
362 #+begin_src clojure | |
363 (fn [a2 sqrtdisc] | |
364 ((fn[root1 root2] | |
365 (do (println root1) | |
366 (println root2) | |
367 (println (+ root1 root2)))) | |
368 (/ (+ (- b) sqrtdisc) a2) | |
369 (/ (- (- b) sqrtdisc) a2)) | |
370 | |
371 (* 2 a) | |
372 (sqrt (- (* b b) (* 4 a c)))) | |
373 #+end_src | |
374 This technique can be extended to handle all simple variable assignments which | |
375 appear as statements in blocks, even if arbitrary G0 T0 statements also appear | |
376 in such blocks. {Note Mccarthywins} | |
377 | |
378 For example, here is a program which uses G0 TO statements in the form | |
379 presented before; it determines the parity of a non-negative integer by | |
380 counting it down until it reaches zero. | |
381 | |
382 #+begin_src algol | |
383 begin | |
384 L1: if a = 0 then begin parity := 0; go to L2; end; | |
385 a := a - 1; | |
386 if a = 0 then begin parity := 1; go to L2; end; | |
387 a := a - 1; | |
388 go to L1; | |
389 L2: print(parity); | |
390 #+end_src | |
391 | |
392 This can be expressed in SCHEME: | |
393 | |
394 #+begin_src clojure | |
395 (let | |
396 [L1 (fn [a parity] | |
397 (if (zero? a) (L2 a 0) | |
398 (L3 (dec a) parity))) | |
399 L3 (fn [a parity] | |
400 (if (zero? a) (L2 a 1) | |
401 (L1 (dec a) parity))) | |
402 L2 (fn [a parity] | |
403 (println parity))] | |
404 (L1 a parity)) | |
405 #+end_src | |
406 | |
407 The trick is to pass the set of variables which may be altered as arguments to | |
408 the label functions. {Note Flowgraph} It may be necessary to introduce new | |
409 labels (such as L3) so that an assignment may be transformed into the binding | |
410 for a function call. At worst, one may need as many labels as there are | |
411 statements (not counting the eliminated assignment and GO TO statements). | |
412 | |
413 ** Compound Expressions ' | |
414 At this point we are almost in a position to model the most general | |
415 form of compound statement. In LISP, this is called the 'PROG feature". In | |
416 addition to statement sequencing and G0 T0 statements, one can return a /value/ | |
417 from a PROG by using the RETURN statement. | |
418 | |
419 Let us first consider the simplest compound statement, which in SCHEME | |
420 we call BLOCK. Recall that | |
421 =(BLOCK s1 sz)= is defined to be =((lambda (dummy) s2) s1)= | |
422 | |
423 Notice that this not only performs Sl before S2, but also returns the value of | |
424 S2. Furthermore, we defined =(BLOCK S1 S2 ... Sn)= so that it returns the value | |
425 of =Sn=. Thus BLOCK may be used as a compound expression, and models a LISP | |
426 PROGN, which is a PROG with no G0 T0 statements and an implicit RETURN of the | |
427 last ``statement'' (really an expression). | |
428 | |
429 Host LISP compilers compile D0 expressions by macro-expansion. We have | |
430 already seen one way to do this in SCHEME using only variable binding. A more | |
431 common technique is to expand the D0 into a PROG, using variable assignments | |
432 instead of bindings. Thus the iterative factorial program: | |
433 | |
434 #+begin_src clojure | |
435 (oarxnz FACT . | |
436 (LAMaoA (n) . | |
437 (D0 ((M N (- H 1)) | |
438 (Ans 1 (* M Ans))) | |
439 ((- H 0) A"$)))) | |
440 #+end_src | |
441 | |
442 would expand into: | |
443 | |
444 #+begin_src clojure | |
445 (DEFINE FACT | |
446 . (LAMeoA (M) | |
447 (PRO6 (M Ans) | |
448 (sszro M n | |
449 Ans 1) ~ | |
450 LP (tr (- M 0) (RETURN Ans)) | |
451 (ssero M (- n 1) | |
452 Ans (' M Ans)) | |
453 (60 LP)))) | |
454 #+end_src | |
455 | |
456 where SSETQ is a simultaneous multiple assignment operator. (SSETQ is not a | |
457 SCHEME (or LISP) primitive. It can be defined in terms of a single assignment | |
458 operator, but we are more interested here in RETURN than in simultaneous | |
459 assignment. The SSETQ's will all be removed anyway and modelled by lambda | |
460 binding.) We can apply the same technique we used before to eliminate G0 T0 | |
461 statements and assignments from compound statements: | |
462 | |
463 #+begin_src clojure | |
464 (DEFINE FACT | |
465 (LAHBOA (I) | |
466 (LABELS ((L1 (LAManA (M Ans) | |
467 (LP n 1))) | |
468 (LP (LAMaoA (M Ans) | |
469 (IF (- M o) (nztunn Ans) | |
470 (£2 H An$)))) | |
471 (L2 (LAMaoA (M An ) | |
472 (LP (- " 1) (' H flN$))))) | |
473 (L1 NIL NlL)))) | |
474 #+end_src clojure | |
475 | |
476 We still haven't done anything about RETURN. Let's see... | |
477 - ==> the value of (FACT 0) is the value of (Ll NIL NIL) | |
478 - ==> which is the value of (LP 0 1) | |
479 - ==> which is the value of (IF (= 0 0) (RETURN 1) (L2 0 1)) | |
480 - ==> which is the value of (RETURN 1) | |
481 | |
482 Notice that if RETURN were the /identity | |
483 function/ (LAMBDA (X) X), we would get the right answer. This is in fact a | |
484 general truth: if we Just replace a call to RETURN with its argument, then | |
485 our old transformation on compound statements extends to general compound | |
486 expressions, i.e. PROG. | |
487 | |
488 * Continuations | |
489 Up to now we have thought of SCHEME's LAMBDA expressions as functions, | |
490 and of a combination such as =(G (F X Y))= as meaning ``apply the function F to | |
491 the values of X and Y, and return a value so that the function G can be | |
492 applied and return a value ...'' But notice that we have seldom used LAMBDA | |
493 expressions as functions. Rather, we have used them as control structures and | |
494 environment modifiers. For example, consider the expression: | |
495 =(BLOCK (PRINT 3) (PRINT 4))= | |
496 | |
497 This is defined to be an abbreviation for: | |
498 =((LAMBDA (DUMMY) (PRINT 4)) (PRINT 3))= | |
499 | |
500 We do not care about the value of this BLOCK expression; it follows that we | |
501 do not care about the value of the =(LAMBDA (DUMMY) ...)=. We are not using | |
502 LAMBDA as a function at all. | |
503 | |
504 It is possible to write useful programs in terms of LAHBDA expressions | |
505 in which we never care about the value of /any/ lambda expression. We have | |
506 already demonstrated how one could represent any "FORTRAN" program in these | |
507 terms: all one needs is PROG (with G0 and SETQ), and PRINT to get the answers | |
508 out. The ultimate generalization of this imperative programing style is | |
509 /continuation-passing/. (Note Churchwins} . | |
510 | |
511 ** Continuation-Passing Recursion | |
512 Consider the following alternative definition of FACT. It has an extra | |
513 argument, the continuation, which is a function to call with the answer, when | |
514 we have it, rather than return a value | |
515 | |
516 #+begin_src algol. | |
517 procedure Inc!(n, c); value n, c; | |
518 integer n: procedure c(integer value); | |
519 if n-=0 then c(l) else | |
520 begin | |
521 procedure l(!mp(d) value a: integer a; | |
522 c(mm); | |
523 Iacl(n-1, romp): | |
524 end; | |
525 #+end_src | |
526 | |
527 #+begin_src clojure | |
528 (in-ns 'categorical.imperative) | |
529 (defn fact-cps[n k] | |
530 (if (zero? n) (k 1) | |
531 (recur (dec n) (fn[a] (k (* n a)))))) | |
532 #+end_src clojure | |
533 It is fairly clumsy to use this version of‘ FACT in ALGOL; it is necessary to | |
534 do something like this: | |
535 | |
536 #+begin_src algol | |
537 begin | |
538 integer ann | |
539 procedure :emp(x); value 2:; integer x; | |
540 ans :1 x; | |
541 ]'act(3. temp); | |
542 comment Now the variable "am" has 6; | |
543 end; | |
544 #+end_src | |
545 | |
546 Procedure =fact= does not return a value, nor does =temp=; we must use a side | |
547 effect to get the answer out. | |
548 | |
549 =FACT= is somewhat easier to use in SCHEME. We can call it like an | |
550 ordinary function in SCHEME if we supply an identity function as the second | |
551 argument. For example, =(FACT 3 (LAMBDA (X) X))= returns 6. Alternatively, we | |
552 could write =(FACT 3 (LAHBDA (X) (PRINT X)))=; we do not care about the value | |
553 of this, but about what gets printed. A third way to use the value is to | |
554 write | |
555 =(FACT 3 (LAMBDA (x) (SQRT x)))= | |
556 instead of | |
557 =(SQRT (FACT 3 (LAMBDA (x) x)))= | |
558 | |
559 In either of these cases we care about the value of the continuation given to | |
560 FACT. Thus we care about the value of FACT if and only if we care about the | |
561 value of its continuation! | |
562 | |
563 We can redefine other functions to take continuations in the same way. | |
564 For example, suppose we had arithmetic primitives which took continuations; to | |
565 prevent confusion, call the version of "+" which takes a continuation '++", | |
566 etc. Instead of writing | |
567 =(- (+ B Z)(* 4 A C))= | |
568 we can write | |
569 #+begin_src clojure | |
570 (in-ns 'categorical.imperative) | |
571 (defn enable-continuation "Makes f take a continuation as an additional argument."[f] | |
572 (fn[& args] ((fn[k] (k (apply f (reverse (rest (reverse args)))))) (last args)) )) | |
573 (def +& (enable-continuation +)) | |
574 (def *& (enable-continuation *)) | |
575 (def -& (enable-continuation -)) | |
576 | |
577 | |
578 (defn quadratic[a b c k] | |
579 (*& b b | |
580 (fn[x] (*& 4 a c | |
581 (fn[y] | |
582 (-& x y k)))))) | |
583 #+end_src | |
584 | |
585 where =k= is the continuation for the entire expression. | |
586 | |
587 This is an obscure way to write an algebraic expression, and we would | |
588 not advise writing code this way in general, but continuation-passing brings | |
589 out certain important features of the computation: | |
590 - [1] The operations to be performed appear in the order in which they are | |
591 performed. In fact, they /must/ be performed in this | |
592 order. Continuation- passing removes the need for the rule about | |
593 left-to-right argument evaluation{Note Evalorder) | |
594 - [2] In the usual applicative expression there are two implicit | |
595 temporary values: those of =(* B B)= and =(* 4 A C)=. The first of | |
596 these values must be preserved over the computation of the second, | |
597 whereas the second is used as soon as it is produced. These facts | |
598 are /manifest/ in the appearance and use of the variable X and Y in | |
599 the continuation-passing version. | |
600 | |
601 In short, the continuation-passing version specifies /exactly/ and | |
602 explicitly what steps are necessary to compute the value of the | |
603 expression. One can think of conventional functional application | |
604 for value as being an abbreviation for the more explicit | |
605 continuation-passing style. Alternatively, one can think of the | |
606 interpreter as supplying to each function an implicit default | |
607 continuation of one argument. This continuation will receive the | |
608 value of the function as its argument, and then carry on the | |
609 computation. In an interpreter this implicit continuation is | |
610 represented by the control stack mechanism for function returns. | |
611 Now consider what computational steps are implied by: | |
612 | |
613 =(LAMBDA (A B C ...) (F X Y Z ...))= when we "apply" the LAMBDA | |
614 expression we have some values to apply it to; we let the names A, | |
615 B, C ... refer to these values. We then determine the values of X, | |
616 Y, Z ... and pass these values (along with "the buck", | |
617 i.e. control!) to the lambda expression F (F is either a lambda | |
618 expression or a name for one). Passing control to F is an | |
619 unconditional transfer. (Note Jrsthack) {Note Hewitthack) Note that | |
620 we want values from X, Y, Z, ... If these are simple expressions, | |
621 such as variables, constants, or LAMBDA expressions, the evaluation | |
622 process is trivial, in that no temporary storage is required. In | |
623 pure continuation-passing style, all evaluations are trivial: no | |
624 combination is nested within another, and therefore no ‘hidden | |
625 temporaries" are required. But if X is a combination, say (G P Q), | |
626 then we want to think of G as a function, because we want a value | |
627 from it, and we will need an implicit temporary place to keep the | |
628 result while evaluating Y and Z. (An interpreter usually keeps these | |
629 temporary places in the control stack!) On the other hand, we do not | |
630 necessarily need a value from F. This is what we mean by tail- | |
631 recursion: F is called tail-recursively, whereas G is not. A better | |
632 name for tail-recursion would be "tail-transfer", since no real | |
633 recursion is implied. This is why we have made such a fuss about | |
634 tail-recursion: it can be used for transfer of control without | |
635 making any commitment about whether the expression expected to | |
636 return a value. Thus it can be used to model statement-like control | |
637 structures. Put another way, tail—recursion does not require a | |
638 control stack as nested recursion does. In our models of iteration | |
639 and imperative style all the LAMBDA expressions used for control (to | |
640 simulate GO statements, for example) are called in tail-recursive | |
641 fashion. | |
642 | |
643 ** Escape Expressions | |
644 Reynolds [Reynolds 72] defines the construction | |
645 = escape x in r = | |
646 | |
647 to evaluate the expression $r$ in an environment such that the variable $x$ is | |
648 bound to an escape function. If the escape function is never applied, then | |
649 the value of the escape expression is the value of $r$. If the escape function | |
650 is applied to an argument $a$, however, then evaluation of $r$ is aborted and the | |
651 escape expression returns $a$. {Note J-operator} (Reynolds points out that | |
652 this definition is not quite accurate, since the escape function may be called | |
653 even after the escape expression has returned a value; if this happens, it | |
654 “returns again"!) | |
655 | |
656 As an example of the use of an escape expression, consider this | |
657 procedure to compute the harmonic mean of an array of numbers. If any of the | |
658 numbers is zero, we want the answer to be zero. We have a function hannaunl | |
659 which will sum the reciprocals of numbers in an array, or call an escape | |
660 function with zero if any of the numbers is zero. (The implementation shown | |
661 here is awkward because ALGOL requires that a function return its value by | |
662 assignment.) | |
663 #+begin_src algol | |
664 begin | |
665 real procedure Imrmsum(a, n. escfun)§ p | |
666 real array at integer n; real procedure esciun(real); | |
667 begin | |
668 real sum; | |
669 sum := 0; | |
670 for i :1 0 until n-l do | |
671 begin | |
672 if a[i]=0 then cscfun(0); | |
673 sum :1 sum ¢ I/a[i]; | |
674 enm | |
675 harmsum SI sum; | |
676 end: . | |
677 real array b[0:99]: | |
678 print(escape x in I00/hm-m.mm(b, 100, x)); | |
679 end | |
680 #+end_src | |
681 | |
682 If harmsum exits normally, the number of elements is divided by the sum and | |
683 printed. Otherwise, zero is returned from the escape expression and printed | |
684 without the division ever occurring. | |
685 This program can be written in SCHEME using the built-in escape | |
686 operator =CATCH=: | |
687 | |
688 #+begin_src clojure | |
689 (in-ns 'categorical.imperative) | |
690 (defn harmonic-sum[coll escape] | |
691 ((fn [i sum] | |
692 (cond (= i (count coll) ) sum | |
693 (zero? (nth coll i)) (escape 0) | |
694 :else (recur (inc i) (+ sum (/ 1 (nth coll i)))))) | |
695 0 0)) | |
696 | |
697 #+end_src | |
698 | |
699 This actually works, but elucidates very little about the nature of ESCAPE. | |
700 We can eliminate the use of CATCH by using continuation-passing. Let us do | |
701 for HARMSUM what we did earlier for FACT. Let it take an extra argument C, | |
702 which is called as a function on the result. | |
703 | |
704 #+begin_src clojure | |
705 (in-ns 'categorical.imperative) | |
706 (defn harmonic-sum-escape[coll escape k] | |
707 ((fn[i sum] | |
708 (cond (= i (count coll)) (k sum) | |
709 (zero? (nth coll i)) (escape 0) | |
710 (recur (inc i) (+ sum (/ 1 (nth coll i)))))) | |
711 0 0)) | |
712 | |
713 (let [B (range 0 100) | |
714 after-the-catch println] | |
715 (harmonic-sum-escape B after-the-catch (fn[y] (after-the-catch (/ 100 y))))) | |
716 | |
717 #+end_src | |
718 | |
719 Notice that if we use ESCFUN, then C does not get called. In this way the | |
720 division is avoided. This example shows how ESCFUN may be considered to be an | |
721 "alternate continuation". | |
722 | |
723 ** Dynamic Variable Scoping | |
724 In this section we will consider the problem of dynamically scoped, or | |
725 "fluid", variables. These do not exist in ALGOL, but are typical of many LISP | |
726 implementations, ECL, and APL. He will see that fluid variables may be | |
727 modelled in more than one way, and that one of these is closely related to | |
728 continuation—pass1ng. | |
729 | |
730 *** Free (Global) Variables | |
731 Consider the following program to compute square roots: | |
732 | |
733 #+begin_src clojure | |
734 (defn sqrt[x tolerance] | |
735 ( | |
736 (DEFINE soar | |
737 (LAHBDA (x EPSILON) | |
738 (Pace (ANS) | |
739 (stro ANS 1.0) | |
740 A (coup ((< (ADS (-s x (~s ANS ANS))) EPSILON) | |
741 (nzruau ANS))) ' | |
742 (sero ANS (//s (+5 x (//s x ANS)) 2.0)) | |
743 (60 A)))) . | |
744 This function takes two arguments: the radicand and the numerical tolerance | |
745 for the approximation. Now suppose we want to write a program QUAD to compute | |
746 solutions to a quadratic equation; p | |
747 (perms QUAD | |
748 (LAHDDA (A a c) | |
749 ((LAHBDA (A2 sonmsc) ' | |
750 (LIST (/ (+ (- a) SQRTDISC) AZ) | |
751 (/ (- (- B) SORTOISC) AZ))) | |
752 (' 2 A) | |
753 - (SORT (- (9 D 2) (' 4 A C)) (tolerance>)))) | |
754 #+end_src | |
755 It is not clear what to write for (tolerance). One alternative is to pick | |
756 some tolerance for use by QUAD and write it as a constant in the code. This | |
757 is undesirable because it makes QUAD inflexible and hard to change. _Another | |
758 is to make QUAD take an extra argument and pass it to SQRT: | |
759 (DEFINE QUAD | |
760 (LANODA (A 8 C EPSILON) | |
761 (soar ... EPSILON) ...)) | |
762 This is undesirable because EPSILDN is not really part of the problem QUAD is | |
763 supposed to solve, and we don't want the user to have to provide it. | |
764 Furthermore, if QUAD were built into some larger function, and that into | |
765 another, all these functions would have to pass EPSILON down as an extra | |
766 argument. A third.possibi1ity would be to pass the SQRT function as an | |
767 argument to QUAD (don't laugh!), the theory being to bind EPSILON at the | |
768 appropriate level like this: A U‘ A | |
769 (QUAD 3 A 5 (LAMBDA (X) (SORT X <toIerance>))) | |
770 where we define QUAD as: | |
771 (DEFINE QUAD | |
772 (LAMBDA (A a c soar) ...)) |