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.