annotate categorical/imperative.org @ 2:b4de894a1e2e

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