The Smallest Universal Language
Developed in 1930s by Alonzo Church
- Studied in logic and computer science
Test bed for procedural and functional PLs
- Simple, Powerful, Extensible
The Next 700 Languages
Whatever the next 700 languages turn out to be, they will surely be variants of lambda calculus.
Peter Landin, 1966
Syntax: What Programs Look Like
e ::= x, y, z, ...
| function(x){ return e }
| e1(e2)
Three kinds of expressions
e
Variables
x
,y
,z
(in JSx
,y
,z
)
Function Definitions (Abstraction)
\x -> e
(in JSfunction(x){return e}
or(x) => e
)
Function Call (Application)
e1 e2
(in JSe1(e2)
)
Complete Description
Application Is Left Associative
We write
e1 e2 e3 e4
instead of
(((e1 e2) e3) e4)
We write
\x1 -> \x2 -> \x3 -> \x4 -> e
instead of
\x1 -> (\x2 -> (\x3 -> (\x4 -> e)))
Examples
\x -> x -- The Identity function
-- function (x) { return x ;}
-- (x) => x
\y -> (\x -> x) -- A function that returns the Identity Fun
-- function(y){ return
-- function (x) { return x ;}
-- }
\f -> f (\x -> x) -- A function that applies arg to the Identity Fun
Semantics : What Programs Mean
We define the meaning of a program with simple rules.
- Scope
- α-step (aka. renaming formals)
- β-step (aka. function calls)
Semantics: Scope
The part of a program where a variable is visible
In the expression \x -> e
x
is the newly introduced variablee
is the scope ofx
x
is bound insidee
(\x -> (\y -> x)) x
Semantics: Free vs Bound Variables
y
is free inside e
if
y
appears insidee
, andy
is not bound insidee
Formally,
free(x) = {x}
free(\x -> e) = free(e) - {x}
free(e1 e2) = free(e1) + free(e2)
QUIZ
Let e
be the expression \x -> x (\y -> x y z)
.
Which variables are free in e
?
A. x
B. y
C. z
D. x
, y
E. x
, y
, z
Semantics: α-Equivalence
λ-terms E1
and E2
are α-equivalent if
E2
can be obtained be renaming the bound variables ofE1
or
E1
can be obtained be renaming the bound variables ofE2
Semantics: α-step
Example: The following three terms are α-equivalent
\x -> x =a> \y -> y =a> \z -> z
We write E1 =a> E2
if E1
is α-equivalent to E2
.
- We can say
E1
takes an α-step toE2
.
α-step Makes Scope Clear
We often α-rename to make parameter names unique
For example, instead of
\x -> x (\x -> x) x -- Yucky scope
=a> \x -> x (\y -> y) x -- Scope of bindings crystal clear
Semantics: Function Calls
In the λ-calculus, a “function call” (application) looks like (x -> E1) E2
Function | Argument |
---|---|
\x -> E1 |
E2 |
How do we evaluate the function with the given argument?
- Rename parameters to make them unique
- Substitute all occurrences of
x
inE1
withE2
!
If so, we say that
(\x -> E1) E2
β-steps toE1[x := E2]
and we can write it as
(\x -> E1) E2 =b> E1[x := E2]
Function Calls: β-step Example
Replace occurrences of parameter f
with argument
(\f -> f (f x)) g
=b> g (g x)
No need to rename, bindings already unique
Normal Forms
An redex is a λ-term of the form
(\x -> E1) E2
A λ-term is in normal form if it contains no redexes.
QUIZ
Is the term x
in normal form ?
A. Yes
B. No
QUIZ
Is the term (x y)
in normal form ?
A. Yes
B. No
QUIZ
Is the term (\x -> x) y
in normal form ?
A. Yes
B. No
QUIZ
Is the term x (\y -> y)
in normal form ?
A. Yes
B. No
Semantics: Evaluation
A λ-term E
reduces/evaluates to a normal form E'
if
there is a sequence of steps
E =?> E_1 =?> ... =?> EN =?> E'
where each =?>
is
- An α-step
=a>
or - A β-step
=b>
.
Examples of Evaluation
(\x -> x) E
=b> E
(\f -> f (\x -> x)) (\x -> x)
=a> (\f -> f (\x -> x)) (\y -> y)
=b> (\y -> y) (\x -> x)
=b> (\x -> x)
Non-Terminating Evaluation
(\x -> x x) (\y -> y y)
=b> (\y -> y y) (\y -> y y)
=a> (\x -> x x) (\y -> y y)
Oops, we can write programs that loop back to themselves…
- Self replicating code!
λ-Calculus Review
Super tiny language
E ::= x | \x -> E | (E E)
- Many steps possible, which to take?
- Call-by-name
- Call-by-value
- Call-by need
Church Rosser Theorem
- Regardless of strategy at most one normal form
- i.e. Programs can evaluate to a single result.
λ-calculus and CSE 130?
Whatever the next 700 languages turn out to be, they will surely be variants of lambda calculus.
Huh? What was that Landin fellow going on about?
Programming with the λ-calculus
Real languages have lots of features
- Booleans done
- Branches done
- Records done
- Numbers
- Representation :
n
as\f x -> f (f (f (f x)))
- IsZero
- Representation :
- Arithmetic
- Incr
- Add
- Mul
- Sub
- Functions (ok, we got those)
- Recursion
Lets see how to encode all of the above with the λ-calculus.
Hidden Motive
- Free your mind
- Build intuition about evaluation-by-substitution
QUIZ
let bar = (\x y -> x)
What does (bar apple orange)
evaluate to?
A. bar orange
B. bar apple
C. apple
D. orange
E. bar
λ-calculus: Booleans
What can we do with a Boolean? - Make a binary choice
How can we encode choice as a function? - A Boolean is a function that - Takes two inputs - Returns one of them as output
True and False
let TRUE = \x y -> x -- returns FIRST input
let FALSE = \x y -> y -- returns SECOND input
Here, let NAME = e
means NAME
is an abbreviation for e
- We don’t want to keep re-typing the whole expression out.
QUIZ
Given
let TRUE = \x y -> x
let FALSE = \x y -> y
What does (TRUE apple orange)
evaluate to?
A. apple
B. orange
C. None of the above
λ-calculus: Branches
A branch is a function that takes three inputs
let ITE = \b x y -> ...
- If
b
evaluates toTRUE
returnx
- If
b
evaluates toFALSE
returny
In other languages like C or JavaScript you would write
b ? x : y
How shall we implement ITE
as a λ-expression?
let ITE = \b x y -> b x y
Example: Branches
We want
if TRUE then e1 else e2
to evaluate toe1
Does it?
eval ite_true:
ITE TRUE e1 e2
=d> (\b x y -> b x y) TRUE e1 e2 -- expand def ITE
=b> (\x y -> TRUE x y) e1 e2 -- beta-step
=b> (\y -> TRUE e1 y) e2 -- beta-step
=b> TRUE e1 e2 -- expand def TRUE
=d> (\x y -> x) e1 e2 -- beta-step
=b> (\y -> e1) e2 -- beta-step
=b> e1
Example: Branches
Now you try it! We want
if FALSE then e1 else e2
to evaluate toe2
Can you fill in the blanks to make it happen?
eval ite_false:
ITE FALSE e1 e2
-- fill the steps in!
=*> e2
QUIZ
let TRUE = \p q -> p
let FALSE = \p q -> q
let HAHA = \b -> ITE b FALSE TRUE
What does HAHA TRUE
evaluate to?
A. HAHA TRUE
B. TRUE
C. FALSE
D. HAHA
E. HAHA FALSE
Boolean Operators: NOT
We can develop the Boolean operators from truth tables
b |
NOT b |
---|---|
TRUE | FALSE |
FALSE | TRUE |
We can encode the above as:
let NOT = \b -> ITE b FALSE TRUE
That is, HAHA
is actually the NOT
operator!
Boolean Operators: AND
Similarly, AND b1 b2
is defined by the truth table
b1 |
b2 |
AND b1 b2 |
---|---|---|
FALSE | FALSE | FALSE |
FALSE | TRUE | FALSE |
TRUE | FALSE | FALSE |
TRUE | TRUE | TRUE |
We can encode the truth table as a function
let AND = \b1 b2 -> ITE b1 (ITE b2 TRUE FALSE) FALSE
which can be simplified to
let AND = \b1 b2 -> b1 b2 FALSE
(Can you see why?)
Boolean Operators: OR
Similarly, OR b1 b2
is defined by the truth table
b1 |
b2 |
AND b1 b2 |
---|---|---|
FALSE | FALSE | FALSE |
FALSE | TRUE | TRUE |
TRUE | FALSE | TRUE |
TRUE | TRUE | TRUE |
We can encode the truth table as a function
let OR = \b1 b2 -> ITE b1 TRUE (ITE b2 TRUE FALSE)
which can be simplified to
let OR = \b1 b2 -> b1 TRUE b2
(Can you see why?)
λ-calculus: Records
What can we do with records ?
- Pack two items into a record.
- Get first item.
- Get second item.
Records : API
(PACK v1 v2) -- makes a pair out of v1, v2 s.t.
-- { fst : v1, snd : v2 }
-- (\b -> ITE b v1 v2)
(FST r) -- returns the first element
-- r.fst
-- r TRUE
(SND r) -- returns the second element
-- r.snd
-- r FALSE
such that
FST (PACK v1 v2) = v1
-- var r = { fst : v1, snd : v2}; assert(r.fst === v1)
SND (PACK v1 v2) = v2
-- var r = { fst : v1, snd : v2}; assert(r.snd === v2)
Records: Implementation
A create a record as a function
let PACK = \v1 v2 -> (\b -> ITE b v1 v2)
- Is called with a Boolean
b
- Returns first element if
b
isTRUE
- Returns second element if
b
isFALSE
We access a record by calling it with TRUE
or FALSE
let FST = \r -> r TRUE -- call w/ TRUE, get first value
let SND = \r -> r FALSE -- call w/ FALSE, get second value
Exercise: Records with 3 values?
How can we implement a record that contains three values?
-- let NULL = FALSE
-- {fst : 'orange', snd : FALSE}
-- {fst : 'banana', {fst : 'orange', snd : FALSE}}
-- {fst : 'apple' , {fst : 'banana', {fst : 'orange', snd : FALSE}}}
-- {fst : 'durian'
, {fst : 'apple'
, {fst : 'banana'
, {fst : 'orange'
, snd : FALSE}}}}
-- PACK durian (PACK apple (PACK orange FALSE))
-- PACK (
(PACK durian apple)
(PACK orange banana)
)
snd : 'orange'}
-- {fst : 'apple' , snd : {fst : 'banana', snd : 'orange'}}
-- {fst : 'durian', snd : {fst : 'apple' , snd : {fst : 'banana', snd : 'orange'}}}
-- {fst : {fst : 'durian', snd : 'apple'},
,snd :{fst : 'banana', snd : 'orange'} }
-- {fst : {fst : 'orange', snd : 'apple'}, snd : 'banana' }
-- r.fst.fst === 'orange'
-- r.fst.snd === 'apple'
-- {fst : {fst : {fst : 'orange', snd : 'apple'}, snd : 'banana' }, snd : 'melon'}
let PACK3 = \v1 v2 v3 -> PACK v1 (PACK v2 v3)
let FST3 = \r -> FST r
let SND3 = \r -> FST (SND r)
let THD3 = \r -> SND (SND r)
let BOB ZERO = (PACK ZERO FALSE)
let BOB ONE = PACK ONE (PACK ZERO FALSE)
let BOB TWO = PACK TWO (PACK ONE (PACK ZERO FALSE))
let BOB THREE = PACK THREE (PACK TWO (PACK ONE (PACK ZERO FALSE)))
λ-calculus: Numbers
n f s
means run f
on s
exactly n
times
-- | represent n as \f x. f (f (f ... (f x)
-- '--- n times ---'
let ONE = \f x. f x
let TWO = \f x. f (f x)
let THREE = \f x. f (f (f x))
let FOUR = \f x. f (f (f (f x)))
QUIZ: Church Numerals
Which of these is a valid encoding of ZERO
?
-- A
let ZERO = \f x -> x
-- B
let ZERO = \f x -> f
-- C
let ZERO = \f x -> f x
-- D
let ZERO = \x -> x
-- E
-- none of the above!
λ-calculus: Arithmetic (IsZero
)
Lets implement a small API for numbers:
-- TRUE if n = ZERO and FALSE otherwise
let IsZero = \n -> ...
λ-calculus: Arithmetic (Incr
)
-- Call `f` on `x` one more time than `n` does
let Incr = \n -> (\f x -> ... )
An example!
eval incr_one :
Incr ONE
=d> (\n f x -> f (n f x)) ONE
=b> \f x -> f (ONE f x)
=*> \f x -> f (f x)
=d> TWO
eval incr_two :
Incr TWO
=d> (\n f x -> f (n f x)) TWO
=b> \f x -> f (TWO f x)
=*> \f x -> f (f (f x))
=d> THREE
QUIZ
How shall we implement Plus
?
-- Call `f` on `x` exactly `n + m` times
let Plus = \n m -> ???
eval plus_zero_zero :
Plus ZERO ZERO =~> ZERO
eval plus_two_one :
Plus TWO ONE =~> THREE
eval plus_two_two :
Plus TWO TWO =~> FOUR
A. let Plus = \n m -> (n Incr) m
B. let Plus = \n m -> Incr n m
C. let Plus = \n m -> n m Incr
D. let Plus = \n m -> n (m Incr)
E. let Plus = \n m -> n (Incr m)
λ-calculus: Arithmetic (Plus
)
-- Call `f` on `x` exactly `n + m` times
let Plus = \n m -> ???
An example!
eval plus_zero_zero :
Plus ZERO ZERO
=~> ZERO
eval plus_two_two :
Plus TWO TWO
=~> FOUR
QUIZ
How shall we implement MULT
?
-- Call `f` on `x` exactly `n * m` times
let Mult = \n m -> ???
eval mult_zero_two :
Mult ZERO TWO =~> ZERO
eval mult_two_one :
Mult TWO ONE =~> TWO
eval mult_two_three :
Mult TWO THREE =~> SIX
A. let Mult = \n m -> n Plus m
B. let Mult = \n m -> n (Plus m) ZERO
E. let Mult = \n m -> m (Plus n) ZERO
C. let Mult = \n m -> n (Plus m ZERO)
D. let Mult = \n m -> (n Plus m) ZERO
λ-calculus: Arithmetic (Mult
)
-- Call `f` on `x` exactly `n * m` times
let Mult = \n m -> ???
eval mul_two_three :
mul two three
=*> six
λ-calculus: Recursion
The final frontier …