Lambda Calculus
2026-08-19 09:15
Introduction made the case: names as labels, order independence, recursion, functions as values.
It named the λ calculus but did not define it.
This unit defines it.
No numbers. No booleans. No data structures.
No conditionals. No recursion.
The only thing that exists is a function.
The only thing you can do is apply one to another.
The book warns it will seem disjointed at first.
We build a set of useful functions bit by bit, and they become the building blocks of units 3 and 4.
Each example assumes the previous ones. Do the reductions by hand.
Take something concrete.
Notice a part that could vary.
Replace it with a name to be supplied later.
The result is a function of that name.
A calculation for a particular item at a particular price.
The price could be anything — replace it with a name.
Instead of one answer, a function: something that gives an answer once told the price.
The λ calculus keeps the operation and throws away the wrapping.
Devised as a model for computability — not as a programming language.
Very simple, very powerful, based on pure abstraction.
That is the entire language.
Any sequence of non-blank characters:
\[\lambda x.x \qquad \lambda first.\lambda second.first \qquad \lambda f.\lambda a.(f\ a)\]
The name after λ is the bound variable — like a formal parameter.
The body may be any expression, including another function.
Functions do not have names.
In Pascal the name is always used to refer to the definition. Here a definition can appear directly where it is used.
\[(\lambda x.x\ \lambda a.\lambda b.b)\]
Also called a bound pair. The function expression is applied to the argument expression.
Evaluate the function expression to get a function.
Replace all occurrences of its bound variable in the body by either
Then evaluate the body.
Applicative order
like Pascal call by value
evaluate the argument first
Normal order
like ALGOL 60 call by name
pass it unevaluated
Normal order is more powerful but may be less efficient.
This unit uses normal order throughout.
\[\lambda x . x\]
Returns whatever argument it is applied to.
\[(\lambda x . x\ \lambda x . x) \Rightarrow \lambda x.x\]
\[\lambda s.(s\ s)\]
Applies its argument to its argument.
The body \((s\ s)\) has the name \(s\) as both function expression and argument expression.
\[(\lambda s.(s\ s)\ \lambda x.x)\]
Replace \(s\) by \(\lambda x.x\) in \((s\ s)\):
\[(\lambda x.x\ \lambda x.x)\]
\[\Rightarrow \lambda x.x\]
\[(\lambda s.(s\ s)\ \lambda s.(s\ s))\]
Replace \(s\) by \(\lambda s.(s\ s)\) in \((s\ s)\):
\[(\lambda s.(s\ s)\ \lambda s.(s\ s))\]
The same expression.
Two symbols. No recursion, no loop, no repetition construct.
It computes forever.
\[\lambda func.\lambda arg.(func\ arg)\]
Applied to a first argument, returns a function that applies it to a second.
\[((\lambda func.\lambda arg.(func\ arg)\ \lambda x.x)\ \lambda s.(s\ s))\]
The function expression is itself an application — evaluate it first:
\[\lambda arg.(\lambda x.x\ arg)\]
Now the whole expression is
\[(\lambda arg.(\lambda x.x\ arg)\ \lambda s.(s\ s))\]
\[\Rightarrow (\lambda x.x\ \lambda s.(s\ s)) \Rightarrow \lambda s.(s\ s)\]
Application is already in the grammar.
Making it a value is what lets application be passed around and abstracted over.
Named definitions, infix operations, an IF style conditional…
Syntactic sugaring: the representation changes, the underlying meaning stays the same.
We never modify the calculus, so the existing theory keeps applying.
A def name is an abbreviation, expanded where it appears.
Not a reference resolved at call time.
So a definition mentioning its own name expands forever.
Recursion cannot be had this way. Unit 4 is about escaping this.
Let <argument> stand for any expression:
There is no way to see the answer.
You reduce until you cannot, then recognise the result by comparing it with a definition you already have.
An error shows up as an expression that matches nothing.
identity was lost because first does not appear in the body.
first and second are used before func, to build:
\[\lambda func.((func\ first)\ second)\]
Apply that function to select_first → you get the first component.
Apply it to select_second → you get the second.
A pair is a function.
Next unit: true and false are these two selectors.
\[(\lambda f.(f\ \lambda x.x)\ \lambda s.(s\ s))\]
Three functions, bound variables \(f\), \(x\), \(s\).
\[\Rightarrow (\lambda s.(s\ s)\ \lambda x.x) \Rightarrow (\lambda x.x\ \lambda x.x) \Rightarrow \lambda x.x\]
A variable is bound to occurrences in the body of a function for which it is the bound variable — provided no other function within the body introduces the same name.
Otherwise it is free.
In the body of \(\lambda f.(f\ \lambda f.f)\), which is \((f\ \lambda f.f)\):
The outer \(f\) is in scope except in the scope of the inner \(f\).
\[\lambda g.((g\ \lambda h.(h\ (g\ \lambda h.(h\ \lambda g.(h\ g))))))\ g)\]
The distinction is per occurrence, not per name.
β reduction replaces the bound occurrences and must leave free ones alone.
arg is used as a bound variable name and as a free variable name.
Not intended at all.
The argument arg was substituted into the scope of the bound variable arg.
This is α conversion.
For \(\lambda \langle name1\rangle.\langle body\rangle\):
the name and all free occurrences of it in the body may be replaced by \(\langle name2\rangle\) —
provided \(\langle name2\rangle\) is not the name of a free variable in the function.
The replacement includes the name after the λ itself.
A captured variable still reduces.
It reduces to the wrong thing, silently.
The rule is easy. The noticing is the hard part.
is equivalent to
Which is what <expression> applied to that argument gives directly.
The wrapper carries no meaning.
Only β makes progress.
Three grammar productions. Three conversion rules.
Nothing further is added to the language in the rest of the module.
Everything from here is built inside it.
Conditions, Booleans and Integers starts building.
true and falseNOT, AND, ORNothing but functions underneath.
Lambda Calculus · Elements of Lambda Calculus