The Lambda Calculus

Note

These slides are also available in PDF format: 4:3 PDF, 16:9 PDF, 16:10 PDF.

The Lambda Calculus

The \(λ\)-calculus is a mathematical language of lambda terms bound by a set of transformation rules. The \(λ\)-calculus notation was introduced in the 1930s by Alonzo Church.

Just like programming languages, the \(λ\)-calculus has rules for what is a valid syntax:

Variables:A variable (such as \(x\)) is valid term in the \(λ\)-calculus.
Abstractions:If \(t\) is a term and \(x\) is a variable, then the term \(λx.t\) is a lambda abstraction.
Applications:If \(t\) and \(s\) are terms, then \(ts\) is the application term of \(t\) onto \(s\).

Anonymous Functions

Lambda abstractions define anonymous functions in the \(λ\)-calculus.

A lambda abstraction which takes an \(x\) and returns a \(t\) is written as so:

\[λx.t\]

Example

Suppose in mathematics we define a function \(f(x) = x + 2\). This could be written as \((λ x.x + 2)\) in the \(λ\)-calculus [1]. Of course, this function is anonymous and not bound to the name \(f\).

[1]Of course, we haven’t said that either \(+\) nor \(2\) is valid in lambda calculus yet. We will get to that…

Functions are First Class

In the \(λ\)-calculus, functions are not only first class, they are the only class of objects. In other words, all data in the \(λ\)-calculus are represented as functions.

Currying

Functions in the \(λ\)-calculus may only take one argument, so currying is typically used to write functions with multiple arguments. For example, the function \(f(x, y) = x\) might be written anonymously as:

\[λx.(λy.x)\]

Further, function application is left-associative, so \(fxy\) means \((fx)y\).

Free and Bound Variables

The \(λ\) operator (which creates lambda abstractions) binds a variable to wherever it occurs in the expression.

  • Variables which are bound in an expression are called bound variables
  • Variables which are not bound in an expression are called free variables

Example

With your learning group, identify the free and bound variables in this expression:

\[λx.(λy.zy)(zx)\]

Transformations

\(\alpha\)-conversion:
 Allows variables to be renamed to non-colliding names. For example, \(λx.x\) is \(\alpha\)-equivalent to \(λy.y\).
\(\beta\)-reduction:
 Allows functions to be applied. For example, \((λx.λy.x)(λx.x)\) is \(\beta\)-equivalent to \(λy.(λx.x)\).
\(\eta\)-conversion:
 Allows functions with the same external properties to be substituted. For example, \((λx.(fx))\) is \(\eta\)-equivalent to \(f\) if \(x\) does not appear in \(f\).

Examples: Alpha Equivalence

With your learning group, identify if each of the following are a valid \(\alpha\)-conversion. Turn in your answers on a sheet of paper with all of your names at the end of class for learning group participation credit for today.

  1. \(λx.λx.x \to λy.λy.y\)
  2. \(λx.λx.x \to λy.λx.x\)
  3. \(λx.λx.x \to λy.λx.y\)
  4. \(λx.λy.x \to λy.λy.y\)

Examples: Beta Reductions

Fully \(\beta\)-reduce each of the following expressions:

  1. \((λx.λy.λf.fxy)(λx.λy.y)(λx.λy.x)(λx.λy.y)\)
  2. \((λa.λb.a(λb.λf.λx.f(bfx))b)(λf.λx.fx)(λf.λx.f(fx))\)

Church Numerals

Since all data in the \(λ\)-calculus must be a function, we use a clever convention of functions (called Church numerals) to define numbers:

0:\(λf.λx.x\)
1:\(λf.λx.fx\)
2:\(λf.λx.f(fx)\)
3:\(λf.λx.f(f(fx))\)

… and so on. In fact, the successor to any number \(n\) can be written as:

\[λf.λx.f(nfx)\]

Notice this

Defining numbers as functions in this way allows us to apply a Chuch numeral \(n\) to a function to get a new function that applies the original function \(n\) times.

Shorthand Notations

While it’s not a defined part of the \(λ\)-calculus, we define common shorthands for some features:

  • \(0, 1, 2, \ldots\) are shorthand for their corresponding Church numerals
  • \(\{\text{SUCC}\} = λn.λf.λx.f(nfx)\)

Note

The notation “\(=\)” above is not a part of the \(λ\)-calculus. I’m using it for saying “is shorthand for”.

Addition and Multiplication

Adding \(m\) to \(n\) can be thought of as taking the successor to \(n\), \(m\) times. Using our shorthand \(\text{SUCC}\), this can be written as:

\[\{\text{ADD}\} = λm.λn.(m \,\{\text{SUCC}\}\, n)\]

Similarly, multiplying \(m\) by \(n\) can be thought of as repeating \(\text{ADD}\, n\), \(m\) times and then applying it to \(0\), this can be written as:

\[\{\text{MULT}\} = λm.λn.(m (\{\text{ADD}\}\, n) 0)\]

Boolean Logic

We use the following convention for true and false:

\[\begin{split}\begin{split} \{\text{TRUE}\} &= λx.λy.x \\ \{\text{FALSE}\} &= λx.λy.y \qquad\text{(Church numeral zero}) \end{split}\end{split}\]

From here, we can define some common boolean operators:

\[\begin{split}\begin{split} \{\text{AND}\} &= λp.λq.p q p \\ \{\text{OR}\} &= λp.λq.p p q \\ \{\text{NOT}\} &= λp.p\ \{\text{FALSE}\}\ \{\text{TRUE}\} \\ \{\text{IF}\} &= λp.λa.λb.p a b \\ & \text{ (returns $a$ if the predicate is TRUE, $b$ otherwise)} \end{split}\end{split}\]

Cons Cells

By convention, we will represent a cons cell as a function that applies its argument to the CAR and CDR of the cons cell. This leads to the shorthand:

\[\begin{split}\begin{split} \{\text{CONS}\} &= λx.λy.λf.f x y \\ \{\text{CAR}\} &= λc.c\ \{\text{TRUE}\} \\ \{\text{CDR}\} &= λc.c\ \{\text{FALSE}\} \\ \{\text{NIL}\} &= λx.\{\text{TRUE}\} \\ \end{split}\end{split}\]

Using this, we can define lists:

\[(\{\text{CONS}\}\, 1\ (\{\text{CONS}\}\, 2\ (\{\text{CONS}\}\, 3\ \{\text{NIL}\})))\]

Lambda Calculus Computational Model

  • In order to have a computer do \(\beta\)-reductions, we need to define a model to represent terms on a computer. (AST!)

  • For example: \((λx.λy.xy)(λy.y)\)

  • Could also be written as s-expression:

    (application
      (abstraction
        x
        (abstraction
          y
          (application x y)))
      (abstraction y y))
    
../_images/lambda-ast.svg

Beta Reduction Algorithm (Eager)

To \(\beta\)-reduce a term \(t\):

  1. If \(t\) is a variable, it is already reduced. Return \(t\).
  2. If \(t\) is an application:
    1. \(\beta\)-reduce the left hand and right hand sides and let \(m\) and \(n\) be the results, respectively.
    2. If \(m\) is an abstraction, return the beta reduction of \(m\)’s term with all instances of \(m\)’s variable replaced with \(n\).
    3. Otherwise, return the application of \(m\) onto \(n\).
  3. If \(t\) is an abstraction, \(\beta\)-reduce the term of the abstraction and return the abstraction.

Lazy Algorithm Motivation

While the eager algorithm will produce the correct results, we may end up evaluating terms we did not need to. For example, consider:

\[(λx.λy.y) \underbrace{((λm.λn.m(λn.λf.λx.f(nfx))n)(λf.λx.f(fx))(λf.λx.f(f(fx))))}_{x} \underbrace{(λx.x)}_{y}\]

We don’t want to have to spend all of that work evaluating \(x\) if we did not need to!

Reducible Expressions

Definition

A reducible expression (called redex for short) is an application with an abstraction as its left child.

In order to implement lazy evaluation, we will need to concern ourselves with the left-most of the outer-most redexes.

../_images/redex-tree.svg

Beta Reduction Algorithm (Lazy)

To lazy \(\beta\)-reduce a term:

  1. Find the left-most of the outer-most redexes in the abstract syntax tree and preform a substitution to complete the application.
  2. If any redexes remain, go to step 1.

Is substitution always safe?

Consider the following (naïve) substitution procedure for a term \(t\) for a variable \(v\) with replacement \(r\):

  • If \(t\) is an application, substitute \(v\) for \(r\) onto the left and right sides.
  • If \(t\) is an abstraction whose variable is \(v\), return the abstraction unaltered.
  • If \(t\) is an abstraction whose variable is not \(v\), return the abstraction with \(v\) substituted for \(r\) onto the abstraction’s term.
  • If \(t\) is a variable which is \(v\), return \(r\).
  • If \(t\) is a variable which is not \(v\), return \(v\).

What could possibly go wrong?

Alpha Renames in Substitutions

Suppose we wish to \(\beta\)-reduce the following term:

\[(λx.λy.(λx.λy.xy)(xy))\]

Using the previous (simple) rules of substitution with our \(\beta\)-reduction algoritm, we end up with this:

\[(λx.λy.(λy.(xy)y))\]

Oops. The binding changed! These terms are not equivalent.

How to solve? \(\alpha\)-rename.

Alpha Renaming Condition

It is sufficient to say that we need to preform an \(\alpha\)-rename in order to preform a substitution of \(v\) for \(r\) in \(t\) if all of the below are true:

  1. \(t\) is an abstraction.
  2. \(t\)’s variable occurs in \(r\).
  3. \(t\)’s variable is not \(v\).
  4. \(t\)’s variable appears as a free variable in \(r\).

(revisit example on previous slide)

Lambda Calculus: Where from Here?

  • Subtraction is hard, but doable. Check out the Wikipedia page on Church Numerals for more info.
  • For recursion, we need to reference ourselves in a lambda abstraction. This is done using a Y-combinator.
  • From there, we can use the \(λ\)-calculus to compute the solution to any problem that a Turing machine can.
  • More on all of this in CSCI-561 (Theory of Computation).
  • Many functional programming languages (e.g., Haskell, Scheme, SlytherLisp) are just practical implementations of the \(λ\)-calculus.