Computer Science Theory
Lecture 20: December 3, 2012
Lambda Calculus II
- Evaluation strategies
- Properties of the lambda calculus
- Multiple-argument functions
- Implementing programming language constructs in lambda calculus
- Recursion with the Y combinator
1. Evaluation Strategies
- A subexpression of a lambda expression where a lambda can be applied to an argument is
called a redex (short for reducible expression).
- If there is more than one redex in an expression,
there can be several evaluation orders for an expression.
- For example, the expression (+ (* 1 2) (* 3 4)) has two redexes: (* 1 2) and (* 3 4)
and the expression can be evaluated by evaluating either redex first.
- Lambda calculus uses two basic evaluation orders: normal order and applicative order.
- Normal order evaluation
- We always reduce the leftmost redex of the outermost redex at each step.
- The expression (λy.λz.z)((λx.x x)(λx.x x))
can be reduced to the normal form λz.z by first applying the
function (λy.λz.z) to the argument
((λx.x x)(λx.x x)).
This reduction order, reducing the leftmost outermost redex,
follows normal form evaluation.
- This corresponds to call by name as in Algol 60.
- If an expression has a normal form, then normal order evaluation will always find it.
- Normal order evaluation is sometimes known as lazy evaluation and is the
usual order of evaluation.
- Applicative order evaluation
- Here we always reduce the leftmost innermost redex.
- This corresponds to call by value as in the programming language C.
- In applicative order evaluation actual parameters are evaluated before being passed to a function.
Both the function and the argument are reduced before the argument is substituted into
the body of the function.
- Even though an expression may have a normal form, applicative order evaluation
may fail to find it.
- For example, in the expression (λy.λz.z)((λx.x x)(λx.x x))
if we first try to reduce the innermost redex
((λx.x x)(λx.x x)), we discover it never terminates:
it always evaluates to itself.
- Applicative order is sometimes called eager evaluation.
2. Properties of the Lambda Calculus
- Lambda calculus is Turing-complete: We can translate any Turing-machine
program into an equivalent
lambda-calculus program and vice versa.
- Lambda calculus is the computational model underlying functional
programming languages such as ML and Haskell.
We can construct lambda calculus expressions that simulate
integers, booleans, logic, loops, data structures, etc.
- Church-Rosser properties
- No lambda expression can have more than one normal form.
This is a corollary of the first Church-Rosser theorem:
- Church-Rosser Theorem 1: If expression w can be reduced to one expression x and
and to another expression y, then there always exists
an expression z such that x →* z and y →* z.
- The second Church-Rosser theorem guarantees that normal order
reducion will always find a normal form if it exits:
- Church-Rosser Theorem 2: If expression w can be reduced to a normal form expression x,
then there is a normal order reduction from w to x.
3. Multiple-Argument Functions
- In lambda calculus, every function has one argument.
We represent multiple-argument functions using a technique
called currying where
we represent a two-argument function (f x y) by two applications of
one-argment functions ((f x) y).
- Abbreviating expressions representing function with multiple arguments
- Sometimes a function of two arguments such as (λs.(λz.z))
will be abbreviated as λsz.z. The convention is that s is the first
formal parameter and z the second. As we shall soon see, this function
represents the integer zero.
- This convention can be used for functions with more than two arguments.
The expression λwyx.y(wyx) abbreviates the three-parameter
function λw.λy.λx.y(wyx) which we shall see represents
the successor function.
4. Implementing Programming Language Constructs in Lambda Calculus
- We can construct pure lambda calculus expressions (expressions
with no constants) to represent programming language constructs
such as integers, booleans, arithmetic operations,
logical operations, recursion, etc.
- The integers can be represented using a function to represent zero
and using a successor function "succ(0)" to represent 1, "succ(succ(0))"
to represent 2, and so on.
- 0 is represented by the function λs.λz.z
- 1 is represented by the function λs.λz.s(z)
- 2 is represented by the function λs.λz.s(s(z))
- In lambda calculus we cannot name functions -- we need to repeat the
text of a function every time we use it. For notation convenience we
- 0 as a synonym for the string λs.λz.z
- 1 as a synonym for the string λs.λz.s(z) and so on.
- The successor function
- We define the successor function as λw. λy. λx. y(w y x).
- Applying the successor function to zero, we get
- (λw. λy. λx. y(w y x))(λs.λz.z)
// Here we substituted
x for z in the body z of the function λz.z
→ λy. λx. y((λs.λz.z) y x)
// Here we substituted
(λs.λz.z) for all occurrences of w in the body y(w y x)
→ λy. λx. y((λz.z)x)
// Here we substituted
y (following the function for 0) for s in the body
// Since there is no s in
the body λz.z the substitution returns the body λz.z
- The last expression represents 1.
- We can define add as
- λm. λn. λf. λx. m f (n f x)
- We can define multiply as
- λm. λn. λf. m (n f)
- The boolean value true can be represented by a function that
always selects the first argument:
- The boolean value false can be represented by a function that
always selects the second argument:
- The conditional if p then a else b can be represented by
- if-then-else = λp.λa.λb. p a b
- The logic operators and, or, not can be expressed with if-then-else:
- and = λp.λq. p q p
- or = λp.λq. p p q
- not = λp.λa.λb. p b a
- Equality and inequality tests can be defined analogously.
5. Recursion with the Y Combinator
- Recursive functions can be defined in the lambda calculus using
the fixed-point Y combinator, which is a function that takes a
function G as an argument and returns G(Y G).
That is, Y G = G(Y G).
- With repeated applications we can get G(G(Y G)), G(G(G(Y G))),... .
- The Y combinator has a simple definition:
- Y = λf. (λx. f(x x))(λx. f(x x))
- Note that
- Y G = (λf.(λx.f(xx))(λx.f(xx)))G
- → (λx.G(xx))(λx.G(xx))
- → G((λx.G(xx))(λx.G(xx)))
- = G(Y G)
- The last line follows from Y G = (λx.G(xx))(λx.G(xx))
6. Practice Problems
- Evaluate (succ (succ (succ 0))).
- Evaluate (add 2 3).
- Evaluate (mult 2 3).
- Define a function that tests if its argument is zero.
- Use the Y combinator to sum the integers from 0 to 3.
- Simon Peyton Jones, The Implementation of Functional Programming Languages,
- Stephen Edwards: The Lambda Calculus