Programming Languages and Translators
Lecture 23: April 22, 2013
The Lambda Calculus
- Reduction orders
- The Church-Rosser theorems
- The Y combinator
- Implementing factorial using the Y combinator
- Church numerals
- Other programming language constructs
- The influence of the lambda calculus on functional languages
1. Reduction Orders
- Programming languages use many different techniques to pass parameters to procedures
such as call by value, call by reference, call by value-return, call by name, and so on.
We can model many of these parameter-passing mechanisms using the lambda calculus.
- The order in which reductions are applied within a lambda expression
can affect the final result.
We will use the terms reduction order and evaluation order synonymously.
- A reducible expression (redex) in a lambda expression is a subexpression
that can be reduced using beta reduction.
- An expression that contains no redexes is said to be in normal form.
- Some reduction orders for an expression may yield a normal form
other orders may not. For example, consider the expression
- This expression has two redexes:
A remarkable property of lambda calculus is that every lambda expression
has a unique normal form if one exists.
- The entire expression is a redex in which we can apply the function
((λx.x x)(λx.x x)) to yield the value 1.
- The rightmost subexpression
((λx.x x)(λx.x x))
is also a redex in which
we can apply the function
(λx.x x) to the argument
(λx.x x). But if we do this reduction we get same subexpression:
(λx.x x)(λx.x x) →
(λx.x x)(λx.x x).
Thus, continuing this order of evaluation will not terminate in a normal form.
(λx.1)((λx.x x)(λx.x x))
has the normal form 1.
(λx.x x)(λx.x x) does not have a normal form
because it always evaluates to itself. We can think of this expression as
a representation for an infinite loop.
There are two common reduction orders for lambda expressions:
normal order evaluation and applicative order evaluation.
Normal order evaluation
Applicative order evaluation
- In normal order evaluation we always reduce the leftmost outermost redex at each step.
- The first reduction order above is a normal order evaluation.
- If an expression has a normal form, then normal order evaluation will always find it.
- In applicative order evaluation we always reduce the leftmost innermost redex
at each step.
- The second reduction order above is an applicative order evaluation.
- Thus, even though an expression may have a normal form, applicative order evaluation
may fail to find it.
2. The Church-Rosser Theorems
- A remarkable property of lambda calculus is that every expression has a unique
normal form if one exists.
- Church-Rosser Theorem I: If
e →* f and
e →* g
by any two reduction orders,
then there always exists
a lambda expression
h such that
f →* h
g →* h.
- A corollary of this theorem is that no lambda expression can be reduced to
two distinct normal forms. To see this, suppose
are in normal form. The Church-Rosser theorem says there must be an expression
h such that
g are each reducible
g are in normal form,
they cannot have any redexes so
f = g = h.
- This corollary says that all reduction sequences that terminate will always
yield the same result and that result must be a normal form.
- The term confluent is often applied to a rewriting system that has the
- Church-Rosser Theorem II: If
e →* f and
f is in normal form, then there exists a normal order
reduction sequence from
3. The Y Combinator
Y combinator (sometimes called the paradoxical combinator)
is a function that takes a function
G as an argument and returns
With repeated applications we can get
G(G(YG)), G(G(G(YG))),... .
- We can implement recursive functions using the
Y is defined as follows:
(λf.(λx.f(x x))(λx.f(x x)))
- Let us evaluate
is any expression:
(λf.(λx.f(x x))(λx'.f(x' x'))) G
→ (λx.G(x x))(λx'.G(x' x'))
→ G((λx'.G(x' x'))(λx'.G(x' x')))
↔ G((λf.(λx.f(x x))(λx.f(x x)))G)
YG →* G(YG);
YG reduces to a call of
- We will use
Y to implement recursive functions.
Y is an example of a fixed-point combinator.
4. Implementing Factorial using the Y Combinator
- If we could name lambda abstractions, we could define the
factorial function with the following recursive definition:
FAC = (λn.IF (= n 0) 1 (* n (FAC (- n 1 ))))
IF is a conditional function.
- However, functions in lambda calculus cannot be named; they
- But we can express recursion as the fixed-point of a function
To do this, let us simplify the essence of the problem.
We begin with a skeletal recursive definition:
- By performing beta abstraction on
FAC, we can transform its
FAC = (λf.(λn.(... f ...))) FAC
= G FAC
G = λf.λn.IF (= n 0) 1 (* n (f (- n 1 )))
- Beta abstraction is just the reverse of beta reduction.
- The equation
- says that when the function
G is applied to
the result is
FAC is a fixed-point of
- We can use the Y combinator to implement
- As an example, let compute
FAC 1 = Y G 1
= G (Y G) 1
= λf.λn.IF (= n 0) 1 (* n (f (- n 1 ))))(Y G) 1
→ λn.IF (= n 0) 1 (* n ((Y G) (- n 1 ))))1
→ IF (= n 0) 1 (* n ((Y G) (- 1 1 )))
→ * 1 (Y G 0)
= * 1 (G(Y G) 0)
= * 1((λf.λn.IF (= n 0) 1 (* n (f (- n 1 ))))(Y G 0)
→ * 1((λn.IF (= n 0) 1 (* n ((Y G) (- n 1 ))))0
→ * 1(IF (= 0 0) 1 (* 0 ((Y G) (- 0 1 )))
→ * 1 1
5. Church Numerals
- Church numberals are a way of representing the integers in lambda calculus.
- Church numerals are defined as functions taking two parameters:
0 is defined as
1 is defined as
λf.λx. f x
2 is defined as
λf.λx. f (f x).
3 is defined as
λf.λx. f (f (f x)).
n is defined as
λf.λx. fn x
n has the property that for any lambda expressions
ngy →* gny.
That is to say,
g to be
y n times.
- In lambda calculus, arithmetic functions can be represented by
corresponding operations on Church numerals.
- We can define a successor function
succ of three arguments that adds one
to its first argument:
λn.λf.λx. f (n f x)
- Example: Let us evaluate
(λn.λf.λx. f (n f x)) 0
→ λf.λx. f (0 f x)
= λf.λx. f ((λf.λx. x) f x)
→ λf.λx. f (λx. x) x)
→ λf.λx. f x
- We can define a function
add using the identity
fm º fn
- Example: Let us evaluate
add 1 2:
λm.λn.λf.λx. m f (n f x) 1 2
→ λn.λf.λx. 1 f (n f x) 2
→* λf.λx. f (f (f x))
- The boolean value true can be represented by a function of two arguments that
always selects its first argument:
- The boolean value false can be represented by a function of two arguments that
always selects its second argument:
- An if-then-else statement can be represented
by a function of three arguments
λc.λi.λe. c i e that uses its
c to select either the if-part
or the else-part
- Example: Let us evaluate if true then 1 else 2:
(λc.λi.λe. c i e) true 1 2
→ (λi.λe. true i e) 1 2
→ (λe. true 1 e) 2
→ true 1 2
= (λx.λy.x) 1 2
→ (λy.1) 2
- The boolean operators and, or, and not can be implemented as follows:
and = λp.λq. p q p
or = λp.λq. p p q
not = λp.λa.λb. p b a
- Example: Let us evaluate
(λp.λa.λb. p b a) true (under renaming)
→ λa.λb. true b a
= λa.λb. (λx.λy.x) b a
→ λa.λb. (λy.b) a
→ λa.λb. b
8. Other Programming Language Constructs
- We can readily implement other programming language constructs in lambda calculus.
As an example, here are lambda calculus expressions for various list operations
cons (constructing a list),
head (selecting the first item from a list), and
tail (selecting the remainder of a list after the first item):
cons = λh.λt.λf. f h t
head = λl.l (λh.λt. h)
tail = λl.l (λh.λt. t)
9. The Influence of The Lambda Calculus on Functional Languages
- Our next lecture will be by Maria Taku who will talk about the influence
of the lambda calculus on functional languages and her experiences
implementing the PLT compiler project using OCaml.
10. Practice Problems
(λx.((λw.λz. + w z)1))
((λx. xx)(λx. xx)))
((λy. * y 1) (- 3 2))
using normal order evaluation and applicative order evaluation.
- Give an example of a code optimization transformation that has the Church-Rosser property.
add two three.
mul be the function
mul two three.
Write a lambda expression for the boolean predicate
- Simon Peyton Jones, The Implementation of Functional Programming Languages,
Stephen A. Edwards: The Lambda Calculus