COMS W4115
Programming Languages and Translators
Lecture 23: April 20, 2015
The Lambda Calculus - II
Outline
- The influence of the lambda calculus on programming languages
- The Church-Rosser theorems
- The Y combinator
- Implementing factorial using the Y combinator
- Church numerals
- Arithmetic
- Logic
- Other programming language constructs
1. The Influence of the Lambda Calculus on Programming Languages
- The lambda calculus is the programming model for functional languages
such as Haskell, ML, and OCaml.
- Constructs such as lambda expressions have appeared in many other languages.
- Here are four examples of anonymous functions in lambda form from Python2.7.
The syntax of a lambda function is
- lambda argument_list: expression
- The functions
map()
, filter()
, and reduce()
are built-in functions in Python2.7.
- Here is a lambda function of two arguments that returns their product:
>>> (lambda x, y: x*y)(2, 3)
6
- The function
map(f, seq)
applies the function f
to all
the elements in seq
. Here is a Python2.7 program that squares every member of a list:
>>> squares = map(lambda x: x**2, range(5))
>>> print squares
[0, 1, 4, 9, 16]
- The function
filter(f, list)
will filter out all the elements of list
for which the function f
returns True
.
Here is a Python2.7 program that prints all squares greater than 5 and less than 50:
>>> squares = map(lambda x: x**2, range(8))
>>> sqs_in_range = filter(lambda x: x > 5 and x < 50, squares)
>>> print sqs_in_range
[9, 16, 25, 36, 49]
- The function
reduce(f, seq)
continually applies the function f
to the sequence seq
. It returns a single value.
Here is a Python2.7 program that multiplies the elements in the list
[1,2,3,4,5]
:
>>> reduce(lambda x, y: x*y, [1,2,3,4,5])
120
- The function
reduce
first multiples 1 and 2 and reduces the list to
[2,3,4,5]
. It then multiplies 2 and 3 and reduces the list to
[6,4,5]
, and so on until only the element 120 is left which is returned as the result.
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
and g →* h
.
- A corollary of this theorem is that no lambda expression can be reduced to
two distinct normal forms. To see this, suppose
f
and g
are in normal form. The Church-Rosser theorem says there must be an expression
h
such that f
and g
are each reducible
to h
. Since f
and 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 property.
- Church-Rosser Theorem II: If
e →* f
and
f
is in normal form, then there exists a normal order
reduction sequence from e
to f
.
3. The Y Combinator
- The
Y
combinator (sometimes called the paradoxical combinator)
is a function that takes a function
G
as an argument and returns G(YG)
.
With repeated applications we can get
G(G(YG)), G(G(G(YG))),...
.
- We can implement recursive functions using the
Y
combinator.
Y
is defined as follows:
(λf.(λx.f(x x))(λx.f(x x)))
- Let us evaluate
YG
where G
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)
= G(YG)
- Thus,
YG →* G(YG)
;
that is, YG
reduces to a call of G
on
(YG)
.
- 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 ))))
- where
IF
is a conditional function.
- However, functions in lambda calculus cannot be named; they
are anonymous.
- But we can express recursion as the fixed-point of a function
G
.
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
definition to:
FAC = (λf.(λn.(... f ...))) FAC
= G FAC
- where
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 FAC
,
the result is FAC
.
That is, FAC
is a fixed-point of G
.
- We can use the Y combinator to implement
FAC
:
- As an example, let compute
FAC 1
:
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
→ 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 λf.λx. x
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 g
and
y
, ngy →* gny
.
That is to say, ngy
causes g
to be
applied to y
n times.
6. Arithmetic
- 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
succ 0
:
(λ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
= 1
- We can define a function
add
using the identity
f(m+n) =
fm º fn
as follows:
- 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))
= 3
7. Logic
- The boolean value true can be represented by a function of two arguments that
always selects its first argument:
λx.λy.x
- The boolean value false can be represented by a function of two arguments that
always selects its second argument:
λx.λy.y
- An if-then-else statement can be represented
by a function of three arguments
λc.λi.λe. c i e
that uses its
condition c
to select either the if-part i
or the else-part e
.
- 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
→ 1
- 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
not true
:
(λp.λa.λb. p b a) true
→ λa.λb. true b a
= λa.λb. (λx.λy.x) b a
→ λa.λb. (λy.b) a
→ λa.λb. b
= false
(under renaming)
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
such as
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. Practice Problems
- Evaluate
((λ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.
- Evaluate
FAC 2
.
- Evaluate
succ two
.
- Evaluate
add two three
.
- Let
mul
be the function
- Evaluate
mul two three
.
- Write a lambda expression for the boolean predicate
isZero
and evaluate isZero one
.
10. References
aho@cs.columbia.edu