Lambda Calculus
Motivation: From Python to Pure Math
In modern programming, we are used to defining named functions that operate on built-in data types. For example, in Python:
def add(x, y):
return x + y
But Python also supports anonymous functions, using the lambda keyword:
add = lambda x, y: x + y
Notice what happened here: the fundamental concept of “addition” doesn’t strictly need a name attached to it. It is just a mapping of inputs to an output.
Invented by Alonzo Church in the 1930s, the -calculus asks a radical question: What if we take this to the absolute extreme? What if a programming language had no built-in data types, no state, no memory, and no named functions? There are no integers, no booleans, and no variables to mutate. Everything—literally every piece of data and control flow—is represented purely by anonymous functions and how they are applied to one another.
Computation here is not “mutating a map.” It is a mechanical process of textual substitution.
1. Syntax: The Ultimate Minimalism
The syntax is remarkably tiny. Every valid expression () falls into one of three categories:
- Variables (): A placeholder (e.g., ).
- Abstraction (): A function definition. It binds the variable in the expression . You can read this as “a function that takes an argument and returns .”
- Application (): A function call. It means “apply the function to the argument .”
(Note: Function application is left-associative, so means . Function bodies extend as far right as possible, so means ).
2. The Three Mechanics of Computation
A program runs by rewriting itself until it can’t be rewritten anymore (reaching “normal form”). This relies on three rules:
-Conversion (Renaming)
The name of a bound variable doesn’t matter. A function that takes and returns is identical to a function that takes and returns .
Why it matters: We need to rename variables to avoid “variable capture” (accidentally overwriting a variable from an outer scope when doing substitutions).
-Reduction (Execution)
This is the actual engine of computation. When a function is applied to an argument, you substitute the argument into the function’s body wherever the bound variable appears.
- reads as: “in expression , replace all free occurrences of with .”
- Example:
-Conversion (Extensionality)
Two functions are the same if they yield the same result for all arguments. If a function just wraps another function, the wrapper is redundant.
3. Operational Semantics (Evaluation Strategies)
When you have a complex expression like , what do you reduce first? The function body, or the argument? Let represent a value (a fully evaluated abstraction ).
Call-by-Value (Strict / Eager)
Used by most languages (Python, Java, OCaml). You must fully evaluate the argument into a value before you substitute it into the function.
- Evaluate left side:
- Evaluate right side:
- Apply:
Call-by-Name (Lazy)
Used in languages like Haskell. You pass the argument unevaluated. It only gets evaluated if the function body actually uses it.
- Evaluate left side:
- Apply immediately:
Example: The Infinite Loop Test
To clearly see the difference in practice, imagine a function that ignores its argument, and an argument that loops forever.
- Call-by-Value (e.g., Python):
def ignore(x):
return 0
def loop():
while True: pass
ignore(loop()) # The program freezes here!
Python eagerly evaluates loop() before passing its value to ignore. Since loop() never finishes, the program hangs indefinitely.
- Call-by-Name / Lazy (e.g., Haskell):
ignore x = 0
loop = loop
main = print (ignore loop) -- Instantly prints 0
Haskell passes the unevaluated loop expression directly into ignore. Because the body of ignore never actually uses x, loop is never evaluated, and the program terminates instantly!
4. Church Encodings: Creating Data from Nothing
How is this Turing complete if there are no booleans or numbers? We “fake” them by defining behaviors using pure functions.
Booleans
A boolean represents a choice between two things. So, we define TRUE as a function that takes two arguments and returns the first. FALSE takes two arguments and returns the second.
An IF-statement is just applying the boolean to the two branches:
Example: IF TRUE M N TRUE M N . The control flow emerges entirely from substitution!
Church Numerals
How do we represent numbers? A number is represented by a function that takes another function and an argument , and applies to exactly times.
- (apply zero times)
- (apply once)
- (apply twice)
We can even define a Successor function (SUCC) that takes a Church numeral and returns :
- (Read as: take a number , and apply one more time to the result of applying times to .)
5. Recursion and the Y-Combinator
Here is the ultimate puzzle: How do you write a recursive function (like factorial) if functions don’t have names? You can’t call yourself if you don’t know who you are.
The trick is to pass the function to itself as an argument. To standardize this, logician Haskell1 Curry discovered the Y-Combinator2, a “fixed-point combinator”:
What happens if we apply to some function ? Let’s -reduce it:
- Substitute for :
- Substitute the right block for in the left block:
Notice that the giant block inside the parenthesis is exactly step 2 again! So:
The Magic: evaluates to wrapped around another copy of . If is a step in a recursive function (like one iteration of factorial), automatically unpacks an infinite supply of ‘s whenever needed, effectively creating a while loop or recursion out of thin air, using purely anonymous textual substitution.
Footnotes
-
The Man With Three Languages: Haskell Curry is such a foundational figure in programming language theory that he has three different programming languages named after him: Haskell, Brooks (his middle name), and Curry. Aside from his work on combinatory logic, he is most famous for “Currying”—the technique of transforming a function that takes multiple arguments into a sequence of functions that each take a single argument (e.g., converting
f(x, y)intof(x)(y)). If you’ve ever wondered why the -calculus only deals with single-argument functions, Currying is the mathematical justification that proves a single argument is all you ever really need! ↩ -
The Startup Connection: If “Y Combinator” sounds familiar outside of academic computer science, it’s because Paul Graham chose it as the name for his legendary Silicon Valley startup accelerator (the “wet dream” of many founders, having backed Airbnb, Stripe, Reddit, etc.). The metaphor is intentional: just as the mathematical Y-combinator calculates a “fixed point” that allows a program to bootstrap its own execution out of purely anonymous functions, the accelerator aims to act as the ultimate bootstrap mechanism for startups, helping a company create itself from nothing. ↩