Untyped Lambda Calculus
Table of contents
Entry conditions
Use this lesson when you want to understand computation at its most fundamental level — before types, before programming languages, before machines. The lambda calculus is a formal system where every computation is expressed using only three constructs: variables, function definitions, and function application.
No prior knowledge of logic, type theory, or programming is assumed. If you have written functions in any programming language, the intuitions will be familiar.
The three building blocks
The lambda calculus has exactly three kinds of expression [@barendregt_LambdaCalculus_1984]:
- Variable: — a name that stands for a value.
- Abstraction: — a function. Read: “the function that takes and returns .” The marks the beginning of a function definition, names the input, and is the body.
- Application: — apply function to argument .
That is everything. There are no numbers, no strings, no if statements, no loops. All of these can be encoded using only the three constructs above — but first, we need to understand how computation happens.
Concrete intuition
Think of as a machine with one input slot labeled and an output determined by . When you feed a value into the slot, every inside the machine is replaced by that value, and the machine produces its output.
Example. The identity function:
Takes an input and returns it unchanged. Feed it anything — it gives back the same thing.
Example. A function that applies its argument to itself:
Takes a function and applies to . (Whether this makes sense depends on what is — the untyped calculus does not check.)
Example. A function that takes two arguments (by nesting):
Takes , returns a function that takes and ignores it, returning . This is how multi-argument functions work in the lambda calculus — every function takes exactly one argument, and multi-argument functions are chains of single-argument functions. This technique is called currying.
Beta-reduction: how computation happens
The one computation rule is beta-reduction:
When a function is applied to an argument , substitute for every occurrence of in . The notation means “the expression with every free occurrence of replaced by .”
Worked example. Apply the identity function to the value :
Replace with in the body . Result: .
Worked example. Apply a “make-pair” function:
Replace with in the body . The inner is unaffected — only is replaced. Result: a function that takes and returns .
Worked example. Multiple reductions:
First application substitutes for . Second application substitutes for — but does not appear in the body , so is discarded.
Free and bound variables
A variable is bound in an expression if it appears inside a that introduces it. Otherwise it is free.
In : is bound (introduced by ), is free (not introduced anywhere).
Beta-reduction only replaces free occurrences of the variable. This prevents accidental capture — substituting an expression into a scope where its variables would be accidentally bound by a different .
Alpha-equivalence. The names of bound variables do not matter. and are the same function — they differ only in the name chosen for the input slot. This renaming is called alpha-conversion.
Non-termination
The untyped lambda calculus permits expressions that reduce forever. The classic example is Omega:
Apply to itself: replace with in the body , yielding — which is again. Reduction loops forever.
This is not a bug — it is a feature. The ability to express non-termination is what makes the untyped lambda calculus Turing-complete: it can compute anything a Turing machine can compute. The simply typed lambda calculus eliminates non-termination by adding types, but at the cost of reducing expressiveness.
Church encodings: data from functions
Since the lambda calculus has no built-in data, data must be encoded as functions. These encodings are named after Alonzo Church, who invented the lambda calculus in the 1930s [@hindley_LambdaCalculusCombinators_2008].
Booleans.
A boolean is a function that takes two arguments and selects one. TRUE selects the first; FALSE selects the second. This is an if-then-else in disguise: and .
Natural numbers (Church numerals).
A Church numeral is a function that takes a function and a starting value , and applies to exactly times. The number is the iteration count.
Successor adds one more application of :
Addition composes the iteration counts:
Apply times starting from , then apply more times. The result applies a total of times.
The Y combinator: recursion without names
The lambda calculus has no built-in recursion — a function cannot refer to itself by name, because there are no names (only variables bound by ). The Y combinator solves this:
For any function , reduces to . The Y combinator feeds a function its own fixed point, enabling recursion without self-reference. This is the mechanism behind recursion in functional programming.
Normal forms and reduction strategies
An expression is in normal form if no more beta-reductions can be applied — there are no remaining applications of a -abstraction to an argument.
Not every expression has a normal form ( does not). When an expression does have a normal form, different reduction strategies may or may not find it:
- Normal-order reduction: always reduce the leftmost, outermost application first. This strategy is guaranteed to find the normal form if one exists (by the Church-Rosser theorem).
- Applicative-order reduction: evaluate arguments before applying functions (like most programming languages). This is more efficient when arguments are used multiple times, but it can fail to terminate even when a normal form exists.
The Church-Rosser theorem guarantees that if two different sequences of reductions both reach a normal form, they reach the same normal form. The lambda calculus is confluent — the order of reduction does not affect the final result, only whether you reach it.
Common mistakes
- Forgetting that application is left-associative. means , not . Apply to first, then apply the result to .
- Forgetting that extends as far right as possible. means , not . The body of the includes everything to its right.
- Substituting into bound variables. When reducing , the inner shadows the outer one. The inner is bound by the inner and is not replaced.
- Conflating the untyped calculus with programming. The lambda calculus is a mathematical formalism, not a programming language. It has no runtime, no memory, no I/O. Programming languages implement (fragments of) the lambda calculus, but they are not identical to it.
What comes next
The untyped lambda calculus is maximally expressive but allows nonsense — self-application, non-termination, and expressions with no clear meaning. The simply typed lambda calculus adds a type system that rules out ill-formed expressions at the cost of some expressiveness. That lesson is the next step.
Minimal data
- Three expression forms: variable, abstraction (), application ().
- One computation rule: beta-reduction ().
- Alpha-equivalence: bound variable names are interchangeable.
- Church-Rosser theorem: reduction order does not affect the result (when a result exists).