PART 3: encoding arithmetic and imperative programs in lambda calculus -------------------------------------------------------------------------- Read [.] as "the translation of ." Booleans: [true] == lam t. lam f. t [false] == lam t. lam f. f # a boolean value is a selector, e.g., [true] is lam thenClause. lam elseClause. thenClause [not B] == NOT [B], where NOT == lam b. b [false] [true] # flip b's internals Example: [not true] == NOT [true] == [true] [false] [true] == (lam t. lam f. t) (lam t. lam f. f) (lam t. lam f. t) => (lam f. (lam t. lam f. f)) (lam t. lam f. t) => (lam t. lam f. f) == [false] [if E1 E2 E3] == [E1] [E2] [E3] # when E1 rewrites to a normal-form boolean, then it will select either E2 or E3 [E1 and E2] == where AND == lam b. lam c. b c [false] # that is, E1 and E2 == if E1 E2 false Now, you can code classical propositional logic. Nonnegative integers ("natural numbers"): [0] == lam s. lam z. z [1] == lam s. lam z. s z [2] == lam s. lam z. s (s z) ... [i] == lam s. lam z. s (s ... (s z)...), repeated i times # a nat-value encodes *how many times* some action, s, should be applied to the base value, z. Example: apply negation two times to true: [2] [not] [true] == (lam s. lam z. s (s z))[not] [true] => (lam z. [not] ([not] z)) [true] => [not]([not] [true]) =>* [not] [false] (see above for the detailed steps) =>* [true] [E =0] == EQ0 [E] where EQ0 == lam n. n (lam z. [false]) [true] # uses [E] like a boolean to generate true or false Example: [2 =0] == EQ0 [2] == (lam n. n (lam z. [false]) [true]) [2] => [2] (lam z. [false]) [true] == (lam s. lam z. s (s z)) (lam z. [false]) [true] => (lam z. (lam z. [false]) ( (lam z. [false]) z)) [true] => (lam z. [false]) [true] => [false] (In order to code two-position relational operations, e.g., E1 > E2, we need a bit more machinery, given below.) [E +1] == SUCC [E] where SUCC == lam n. lam s. lam z. s (n s z) # inserts one more occurrence of s Example: [3 +1] == SUCC [3] == (lam n. lam s. lam z. s (n s z)) [3] ==> lam s. lam z. s ( [3] s z ) == lam s. lam z. s ((lam s. lam z. s (s (s z))) s z) => lam s. lam z. s ( (lam z. s (s (s z))) z ) => lam s. lam z. s (s (s (s z))) [E1 + E2] == ADD [E1] [E2] where ADD = lam m. lam n. m SUCC n # applies SUCC E1-many times to E2 Example: [2 + 3] == ADD [2] [3] == [2] SUCC [3] == (lam s. lam z. s (s z)) SUCC [3] => (lam z. SUCC (SUCC z) ) [3] => SUCC (SUCC [3]) == SUCC (SUCC (lam s. lam z. s (s (s z)))) =>* lam s. lam z. s (s (s (s (s z)))) Multiplication is an easy exercise, since it is repeated addition. It takes some effort to code subtraction-by-1 (predecessor). (Use pairs, coded below.) Data structures (pairs): [pair E1 E2] == lam b. b [E1] [E2] [fst E] == [E] [true] [snd E] == [E] [false] Now, you can emulate lists, arrays, trees, etc., as nested pairs Use pairs to define predecessor: [E -1] == PRED [E] where PRED == lam n. (n COUNTUP ([pair 0 0])) [true] and COUNTUP == lam p. [pair] ([snd] p) (SUCC ([snd] p)) # n COUNTUP [pair 0 0] counts upwards n times starting from: 0,0 0,1 1,2 ... to n-1, n The desired answer is n-1, which is extracted using [true]. That is, we compute n-1 by counting upwards from 0 up to n-1. This makes [E1 - E2] == [E2] PRED [E1] # apply PRED E2 times to E1 and [E1 > E2] == [not ((E1 - E2) = 0)] and so on. (Division, modulo, ...) While-language commands: Programs have this syntax: C ::= P ; C | print I P ::= x = E | if E then C1 else C2 | while E do C usingVars I* # The while loop must be annotated with the vars that it updates. # I've done this to match the encoding below with what's already presented # in the earlier note. Example program: x = 3; y = 1; while x > 0 (x = x -1; y = y +1) usingVars x, y; print y [print I] == I [x = E ; C] == (lam x. [C]) [E] # the C is the rest of the program --- the "continuation" [(if E then C1 else C2); C] == [E] [C1; C] [C2; C] # note how the continuation is attached to both arms of the conditional [(while E do C1 usingVar I); C] == Y (lam w. lam I. [E] [C1; (w I)] [C]) I where Y == lam f. (lam z. f (z z))(lam z. f (z z)) # The insight is that a while loop "unfolds" while it executes: while E do C ==> if E then (C; while E do C) else pass This is the behavior that is encoded with Y, which is an unfolder: Y F =>* F (Y F) Y is sometimes called a fixed-point combinator or the "paradoxical combinator" since it behaves like Russell's paradox example. Here, we desire this behavior: Y W I where W == (lam w. lam I. [E] [C1 ; (w I)] [C]) =>* [E] [C1 ; (Y W I)] [C]) that is, the loop unfolds into a if-command with a fresh copy of the loop embedded after the loop-body's code. Example: [x = 3 ; while x > 0 do x = x -1 usingVar x; print x] == (lam x. Y W x) [3] where W == lam w. lam x. [x > 0] [x = x -1 ; (w x)] [print x] == lam w. lam x. [x > 0] ((lam x. (w x)) [x -1]) x => Y W [3] =>* W (Y W) [3] => (lam x. [x > 0] ((lam x. (Y W x) [x -1]) x ) [3] =>* [3 > 0] ((lam x. (Y W x)) [3 -1]) [3] =>* [true] ((lam x. (Y W x)) [3 -1]) [3] =>* (lam x. (Y W x)) [3 -1] =>* Y W [3 -1] =>* Y W [2] # compare this to Y W [3] at loop entry =>* Y W [0] =>* [false] ((lam x. (Y W x)) [0 -1]) [0] =>* [0] This is not the most elegant translation of imperative programs into lambda-calculus. A better one is used by Strachey and is the basis of his denotational semantics methodology.