`eval`

for expressions
based on a notion of `e`

evaluates to an answer `a`

if and only if `e`

reduces to `a`

:
eval(e) = a if and only if e ->* a.The reduction relation

`->*`

is the reflexive
and transitive closure of the simpler reduction relation
`->`

. That is, `->*`

consists of
zero or more `->`

steps.
To define `->`

, we need some preliminary definitions.
*Expressions* `e`

and *values* `v`

are:

e ::= v | x | (e e) v ::= c | (lambda (x) e)

`[]`

. We can define evaluation contexts inductively:
E ::= [] | (E e) | (v E) | (if E e e)For Scheme where applications may have more than one argument, we have

E ::= [] | (v* E e*) | (if E e e).

A ** redex** is one of the following

((lambda (x) e) v) (if v e2 e3) (add1 n) ...Now we can define the reduction relationother primitives

`->`

:
E[((lambda(x) e) v)] -> E[e[x |-> v]] (substitution avoiding capture) E[(if v e2 e3)] -> E[e3] if v = #f E[(if v e2 e3)] -> E[e2] if v != #t E[(add1 n)] -> E[n+1] ...

**Lemma:** For all closed e, either e = v or there is a unique E
and redex such that e = E[redex].

**Lemma:** For e closed, if e -> e' then e' is closed.

From these two lemmas, we get the following rather desirable property:

**Thm:** The relation `->`

is a function.

This is good because it means that a program will always produce the same answer.

We have yet to formally define capture avoiding substitution `|->`

.
Let's do that:

c[x |-> v] = c x[x |-> v] = x y[x |-> v] = y (y != x) (e1 e2)[x |-> v] = (e1[x |-> v] e2[x |-> v]) (lambda (x) e)[x |-> v] = (lambda (x) e) (lambda (y) e)[x |-> v] = (lambda (z) e[y |-> z][x |-> v]) (z not in FV(e) or FV(v))In the last clause, renaming

`y`

to `z`

is how we
avoid capturing free occurrences of `y`

in `v`

when substituting inside the `(lambda (y) ...)`

.
Now let's do an example:

((lambda (x) (x x)) (lambda (y) y y)) -> (x x)[x |-> (lambda (y) (y y))] = ((lambda (y) (y y)) (lambda (y) (y y))) -> (y y)[y |-> (lambda (y) (y y))] = ((lambda (y) (y y)) (lambda (y) (y y))) -> ...At each step above, the evaluation context E is just

`[]`

.
By the way,
since names don't matter it is clear that we are right back where we started.
This is an example of an infinite loop without using letrec.
Here's another example:

(sub1 ((lambda (x) (add1 x)) 1)) E = (sub1 []) -> (sub1 (add1 x)[x |-> 1]) = (sub1 (add1 1)) E = (sub1 []) -> (sub1 2) E = [] -> 1

Scheme programs consist of more than simply single expressions.
`define`

allows us to make definitions at the top level scope. With
this in mind we can augment our reduction rules to handle this situation.

P ::= D* e D ::= (define x v*) D* E[((lambda (x) e) v)] -> D* E[e[x |-> v] D* E[(add1 n)] -> D* E[n+1] ... D1*(define x v)D2* E[x] -> D1*(define x v)D2* E[v]This last rule allows "looking up" the name of a definition.

We now have reduction semantics sufficient to evaluate the following expression:

(define Pi (lambda (l) (if (null? l) 1 (* (car l) (Pi (cdr l)))))) (Pi '(1 2)) E = ([] '(1 2)) :> D((lambda (l) ... ) '(1 2)) E = [] :> D(if (null? '(1 2)) 1 (* (car '(1 2)) (Pi (cdr '(1 2))))) E = (if [] 1 ... ) :> D(if #f 1 (* ...)) ... :> D(* 1 (* 2 1))We can also do the exact same thing for the CPS version of

`Pi`

.
(define CPS-Pi (lambda (l k) (if (null? l) (k 1) (CPS-Pi (cdr l) (lambda (v) (* (car l) v)))))) (CPS-Pi '(1 2) (lambda (x) x)) E = [] :> D((lambda (l) ...) '(1 2) (lambda (x) x)) E = [] :> (if (null? l) 1 (CPS-Pi ...)) E = [] :> (if #f 1 (CPS-Pi ...)) E = [] :> (CPS-Pi (cdr '(1 2)) (lambda (v) ...))Note that E = [] throughout this reduction. This means that no context is built. A call that builds no context is called a

The following is an algorithm for performing CPS.

CPS: expr X expr -> expr CPS(x, k) = (k x) CPS(c, k) = (k c) CPS((lambda (x) e), k) = (k (lambda (x k) CPS(e, k))) CPS((e1 e2), k) = CPS(e1, (lamdba (x) CPS(e2, (lambda (y) (x y k))))) CPS((if e1 e2 e3), k) = CPS(e1, (lambda (x) (if x CPS(e2,k) CPS(e3,k))))Many (but not all) compilers for advanced languages like Scheme and ML use this algorithm to transform all user programs into CPS as one step of compiling. Then all calls in a program are tail calls. Hence no call ever returns and procedure calls thus become jumps with arguments.