Lambda Calculus

λf.f (λa b c.a c (b c)) λd e.d

Lambda Calculus

Lambda calculus is a formal system that creates a simple and universal model of computation.

Formal System

A formal system is a precise collection of rules for manipulating symbols. These are usually organized into axioms, theorems and inference rules.

These systems are completely specified and don't rely on any external knowledge.

History

Published by Alonso Church in 1936 CE, it slightly predates Turing Machines. Both can compute any generally recursive function.

Definition

A brief overview (notation will be explained later)

  • Variables: v := [^λ.()\s]+
  • Expressions: E := v | ( E ) | λ v . E | E E
  • Free Variables: FV[E] ⊂ v
  • Equivalence: b ∉ FV[E] ⟹ λa.E ≑ λb.(E [a := b])
  • Reduction: (λa.E) F ... ⇾ E [a := F] ...

This is everything in lambda calculus.

Variables

v := [^λ.()\s]+

A variable is a sequence of characters excluding λ, ., (, ) and white space. They serve as labels and focal points for substitution.

Examples: a b c MULTIPLY 172 IS-NIL? @!

Expressions

E := v | ( E ) | λ v . E | E E

  • Variable: v
  • Grouping: (E)
  • Abstraction: λv.E
  • Application: E E

Application is conventionally left associative.
E F G is the same as (E F) G
but not E (F G)

Free Variables

A free variable is one not bound by abstraction. This is how we determine which variables are free:

  • FV[a] ≝ {a}
  • FV[(E)] ≝ FV[E]
  • FV[λa.E] ≝ FV[E] - {a}
  • FV[E F] ≝ FV[E] ∪ FV[F]

Every expression is associated with exactly one set of free variables, which may be empty.

Free Variables: Examples

  • FV[a b c] = ({a} ∪ {b}) ∪ {c} = {a, b, c}
  • FV[λa.a] = {a} - {a} = {}
  • FV[λa.a b] = {a, b} - {a} = {b}
  • FV[(λb.b) b] = {} ∪ {b} = {b}
  • FV[(λa.a b) c] = ({a, b} - {a}) ∪ {c} = {b, c}
  • FV[(λa.a b) λb.c b] = {b} ∪ {c} = {b, c}
  • FV[λa.λb.b a] = {a, b} - {b} - {a} = {}

Substitution

Replaces free variable with expression: [a := G]

  • a [a := G] ≝ G ∧ b [a := G] ≝ b
  • (E) [a := G] ≝ (E [a := G])
  • E F [a := G] ≝ (E [a := G]) (F [a := G])
  • λa.E [a := G] ≝ λa.E ∧ (b ∉ FV[G]) ⟹
    λb.E [a := G] ≝ λb.(E [a := G])

    (b ∈ FV[G] ∧ c ∉ FV[EG)] ⟹
    λb.E [a := G] ≝ λc.(E [b := c, a := G])

Substitution: Examples

  • a b (a b) [a := c] ⇝ c b (c b)
  • a b (a b) [a := λb.b] ⇝ (λb.b) b ((λb.b) b)
  • (λa.a b) λb.a b [a := c] ⇝ (λa.a b) λb.c b
  • (λa.a b) λb.a b [a := b] ⇝ (λa.a b) λc.b c
  • (λa.a b) λb.a b [a := λa.b] ⇝ (λa.a b) λc.(λa.b) c

Equivalence

Replacing all occurances of a bound variable does not change the meaning of an expression as long as free variables are preserved.

b ∉ FV[E] ⟺ λa.E ≑ λb.(E [a := b])

EF ⟹ FV[E] = FV[F]

Equivalence: Examples

  • λa.a ≑ λb.b
  • λa.a b c ≑ λd.d b c
  • λf.(λa.f (a a)) λa.f (a a) ≑
    λg.(λb.g (b b)) λc.g (c c)
  • λa.a a ≠ λb.b
  • λa.a b c ≠ λb.a b c
  • λa.a b c ≠ λb.b b c
  • λa.a b c ≠ λd.b c d

Reduction

Reduction applies an abstration to the expression that follows. This replaces the abstraction and following expression with the body of the abstraction with the bound variable substituted.

(λa.E) F ... E [a := F] ...

Reduction: Examples

  • (λa.a) b ⇝ a [a := b] ⇾ b
  • (λa.a a) b c ⇝ (a a [a := b]) c ⇾ b b c
  • λa.(λb.b a) c ⇝ λa.(b a [b := c]) ⇾ λa.c a

Reduction: Capture

Free variables in the application argument must not match bound variables in nested abstractions.

(λa.b) b ⇝ b [a := b] ⇾ b

(λa.λb.a) b ⇝ λb.a [a := b] ⇾ λb.b

(λa.λc.a) b ⇝ λc.a [a := b] ⇾ λc.b

We must rename bound variables sometimes.

Normal Form

An expression is said to be in normal form if there are no reductions that can be performed.

The Church-Rosser theorem proves that if an expression has a normal form there is only one. When more than one reduction is possible the order will not result in different final forms.

Normal Form: Examples

  • a
  • λa.a
  • a (λb.b) c
  • (λa.a) λb.λc.b ⇝ a [a := λb.λc.b] ⇾ λb.λc.b
  • (λa.a) ((λb.b) λc.c) ⇝ a [a := (λb.b) λc.c]
    (λb.b) λc.c
    ⇝ b [b := λc.c] ⇾ λc.c

Ad Infinitum

Not all expressions have a normal from.

(λa.a a) λa.a a ⇝ a a [a := λa.a a]
(λa.a a) λa.a a
⇾ (λa.a a) λa.a a
(λa.a a) λa.a a
⇾ ...

This is a simple example, but there could be cycles or complex sequences that don't terminate.

Normal Order

Normal order performs the outer most, left most reduction first when more than one are available.

(λa.a a) ((λb.b) c) ⇝ a a [a := ((λb.b) c)]
((λb.b) c) ((λb.b) c)
⇝ b [b := c] ((λb.b) c)
c ((λb.b) c)
⇝ c (b [b := c]) ⇾ c c

Normal order is guaranteed to reach a normal form for any expression if one exists.

Applicative Order

A disadvantage of normal form is that it can evaluate parts of an expression many times. Applicative evaluation solves this problem.

(λa.a a) ((λb.b) c) ⇝ (λa.a a) (b [b := c])
(λa.a a) c
⇝ a a [a := c] ⇾ c c

Only two reductions were necessary.

A Little Too Eager

Sometimes applicative order fails...

(λa.λb.a) c ((λd.d d) λd.d d)

    Normal Order
  • ⇾ (λb.c) ((λd.d d) λd.d d)
  • ⇾ c
    Applicative Order
  • ⇾ (λa.λb.a) c ((λd.d d) λd.d d)
  • ⇾ (λa.λb.a) c ((λd.d d) λd.d d)
  • ⇾ (λa.λb.a) c ((λd.d d) λd.d d)
  • ...

Stable or efficient -- pick one!

Lazy Evaluation

Lazy evaluation is like normal order but it keeps of expressions so they're evaluated only once.

  • (λa.a a) ((λb.b) c) ⇝ a a [a := ((λb.b) c)]
    ((λb.b) c) ((λb.b) c) ⇾ c c

Stable, efficient... and tricky to implement.

Currying

Frege (1893 CE) pointed out that single argument functions can emulate multi-argument functions.

  • (λa.λb.E) F G ⇾ (λb.(E [a := F])) G E [a := F, b := G]
  • (λa.λb.λc.E) F G H ⇾ (λb.λc.(E [a := F])) G H
    (λc.(E [a := F, b := G])) H
    E [a := F, b := G, c := H]
  • (λa.λb.E) F ⇾ λb.(E [a := F])

Supplying some but not all arguments is allowed.

Multi-Argument Notation

Nested abstractions have a convenient short hand.

λa b.E ≝ λab.E

λa b c.E ≝ λabc.E

...

Spaces separate variables.

Lambda Calculus

  • Variables: v := [^λ.()\s]+
  • Expressions: E := v | ( E ) | λ v . E | E E
  • Free Variables: FV[E] ⊂ v
  • Equivalence: b ∉ FV[E] ⟹ λa.E ≑ λb.(E [a := b])
  • Reduction: (λa.E) F ... ⇾ E [a := F] ...
  • Left-Associative: E F G ≑ (E F) G
  • Multi-Argument: λa b.E ≝ λab.E

Lambda Playground

This is a JavaScript implementation.

Lambda Computation

Lambda calculus is certainly simple. But isn't it supposed to be universal?

Missing Features

Some things lambda calculus seems to lack:

  • Boolean Logic
  • Numbers
  • Arithmetic
  • Flow Control
  • Data Structures

We can construct these!

Boolean Logic

One thing we might want to compute is boolean logic. Everything must evaluate to true or false. How could we represent these in lambda calculus?

  • TRUE ≝ λa b.a
  • FALSE ≝ λa b.b

Conditionals

IF p THEN ELSE

  • (λp a b.p a b) p THEN ELSE ⇾ p THEN ELSE
  • TRUE THEN ELSE ⇾ (λa b.a) THEN ELSE ⇾ THEN
  • FALSE THEN ELSE ⇾ (λa b.b) THEN ELSE ⇾ ELSE

We don't need a conditional operator.

Accentuate the Negative

NOT: λp a b.p b a

  • NOT TRUE
  • λa b.TRUE b a
  • λa b.b ⇾ FALSE
  • NOT FALSE
  • λa b.FALSE b a
  • λa b.a ⇾ TRUE

Conjunction Junction

AND: λp q.p q p

  • AND TRUE TRUE
  • TRUE TRUE TRUE
  • TRUE
  • AND TRUE FALSE
  • TRUE FALSE TRUE
  • FALSE
  • AND FALSE TRUE
  • FALSE TRUE FALSE
  • FALSE
  • AND FALSE FALSE
  • FALSE FALSE FALSE
  • FALSE

Disjunction Junction

OR: λp q.p p q

  • OR TRUE TRUE
  • TRUE TRUE TRUE
  • TRUE
  • OR TRUE FALSE
  • TRUE TRUE FALSE
  • TRUE
  • OR FALSE TRUE
  • FALSE FALSE TRUE
  • TRUE
  • OR FALSE FALSE
  • FALSE FALSE FALSE
  • FALSE

Equality for Booleans

BOOLEQ?: λp q.p q (NOT q)

  • BOOLEQ? TRUE TRUE
  • TRUE TRUE (NOT TRUE)
  • TRUE
  • BOOLEQ? TRUE FALSE
  • TRUE FALSE (NOT FALSE)
  • FALSE
  • BOOLEQ? FALSE TRUE
  • FALSE TRUE (NOT TRUE)
  • NOT TRUE ⇾ FALSE
  • BOOLEQ? FALSE FALSE
  • FALSE FALSE (NOT FALSE)
  • NOT FALSE ⇾ TRUE

Review: Boolean Logic

  • TRUE: λa b.a
  • FALSE: λa b.b
  • NOT: λp a b.p b a
  • AND: λp q.p q p
  • OR: λp q.p p q
  • BOOLEQ?: λp q.p q (NOT q)

Numbers

Church Numerals represent numbers with repeated application. Conceptually: n = λf a.fn a

  • ONE: λf a.f a
  • TWO: λf a.f (f a)
  • THREE: λf a.f (f (f a))
  • ...

Noting Succeeds like Successor

Natural numbers consist of zero and its successors.

ZERO: λf a.a [FALSE]

SUCCESSOR: λn f a.f (n f a)

Successor: Examples

SUCCESSOR: λn f a.f (n f a)

  • SUCCESSOR ZERO
  • λf a.f (ZERO f a)
  • λf a.f ((λf a.a) f a)
  • λf a.f a [ONE]
  • SUCCESSOR ONE
  • λf a.f (ONE f a)
  • λf a.f ((λf a.f a) f a)
  • λf a.f (f a) [TWO]

And so on.

Addition

Take the successor repeatedly to add:

ADD: λm n.n SUCCESSOR m

  • ADD THREE TWO
  • TWO SUCCESSOR THREE
  • SUCCESSOR (SUCCESSOR THREE)
  • SUCCESSOR FOUR
  • FIVE

Multiplication

Compose numbers to multiply:

MULTIPLY: λm n f.m (n f)

  • MULTIPLY THREE TWO
  • λf.THREE (TWO f)
  • λf.(λf a.f (f (f a))) ((λf a.f (f a)) f)
  • λf.(λf a.f (f (f a))) (λa.f (f a))
  • λf.λa.f (f (f (f (f (f a)))))
  • λf a.f (f (f (f (f (f a))))) [SIX]

Exponentiation

Raise one number to the power of another:

POWER: λn m.m n

  • POWER THREE TWO
  • TWO THREE
  • (λf a.f (f a)) λf a.f (f (f a))
  • ...
  • λf a.f (f (f (f (f (f (f (f (f a)) [NINE]

Exponentiation: Demo

Review: Numbers and Arithmetic

  • ZERO: λf a.a [FALSE]
  • SUCCESSOR: λn f a.f (n f a)
  • ADD: λm n.n SUCCESSOR m
  • MULTIPLY: λm n f.m (n f)
  • POWER: λn m.m n

Subtraction

Subtraction is addition reversed.

SUBTRACT: λm n.n PREDECESSOR m

PREDECESSOR: λn f a.n (λg h.h (g f)) (λc.a) λb.b

Predecessor Two

  • (λn f a.n (λg h.h (g f)) (λc.a) λb.b) λf a.f (f a)
  • λf a.(λf a.f (f a)) (λg h.h (g f)) (λc.a) λb.b
  • λf a.(λg h.h (g f)) ((λg h.h (g f)) (λc.a)) λb.b
  • λf a.(λh.h (((λg h.h (g f)) (λc.a)) f)) λb.b
  • λf a.(λb.b) (((λg h.h (g f)) (λc.a)) f)
  • λf a.((λg h.h (g f)) (λc.a)) f
  • λf a.(λh.h ((λc.a) f)) f
  • λf a.f ((λc.a) f) ⇾ λf a.f a [ONE]

Predecessor One

  • (λn f a.n (λg h.h (g f)) (λc.a) λb.b) λf a.f a
  • λf a.(λf a.f a) (λg h.h (g f)) (λc.a) λb.b
  • λf a.(λg h.h (g f)) (λc.a) λb.b
  • λf a.(λh.h ((λc.a) f)) λb.b
  • λf a.(λb.b) ((λc.a) f)
  • λf a.((λc.a) f) ⇾ λf a.a [ZERO]

Predecessor Zero

  • (λn f a.n (λg h.h (g f)) (λc.a) λb.b) λf a.a
  • λf a.(λf a.a) (λg h.h (g f)) (λc.a) λb.b
  • λf a.(λc.a) λb.b ⇾ λf a.a [ZERO]

Zero has no predecessor but our operator reduces to zero. This isn't strictly correct but is useful.

Division

Division is multiplication reversed.

DIVIDE: λn.((λf.(λa.f (a a)) λa.f (a a)) λc n m f a.(λd.(λn.n (λa.λa b.b) λa b.a) d a (f (c d m f a)))
((λm n.n (λn f a.n (λg h.h (g f)) (λc.a) λb.b) m) n m)) ((λn f a.f (n f a)) n)

Any questions? Of course not. Moving on...

Zero Test

We can ask questions about numbers:

IS-ZERO?: λn.n (λa.FALSE) TRUE

  • IS-ZERO? ZERO
  • ZERO (λa.FALSE) TRUE
  • (λf a.a) (λa.FALSE) TRUE
  • TRUE
  • IS-ZERO? ONE
  • (λf a.f a) (λa.FALSE) TRUE
  • (λa.FALSE) TRUE
  • FALSE

What happens if we ask about a successor of one?

Even

How can we ask whether a number is even?

IS-EVEN?: λn.n NOT TRUE

  • IS-EVEN? ZERO
  • ZERO NOT TRUE
  • (λf a.a) NOT TRUE
  • TRUE
  • IS-EVEN? ONE
  • (λf a.f a) NOT TRUE
  • NOT TRUE
  • FALSE
  • IS-EVEN? TWO
  • (λf a.f (f a)) NOT TRUE
  • NOT (NOT TRUE)
  • TRUE

How could we define IS-ODD?

Comparisons

  • LESSEQ?: λn m.IS-ZERO? (SUBTRACT n m)
  • GREATEREQ?: λn m.IS-ZERO? (SUBTRACT m n)
  • EQUAL?: λn m.AND (LESSEQ? n m) (LESSEQ? m n)
  • LESS?: λn m.NOT (GREATEREQ? n m)
  • GREATER?: λn m.NOT (LESSEQ? n m)

Comparisons: Demo

Flow Control

We can easily use a Church numeral directly to loop a fixed number of times.

However, we still don't have a way to stop a loop when some condition is met. That's an important gap in our programming ability.

Lambda Loops

These require statements, which we don't have. See recursion.

Lambda Recursion

These require named functions, which we don't have. See loops.

Fixed Point Combinator

Normal order fixed point (or Y) combinator:

FIX ≝ λf.(λa.f (a a)) λa.f (a a)

FIX g ⇾ (λa.g (a a)) λa.g (a a)
g ((λa.g (a a)) λa.g (a a))
≑ g (FIX g)
g (g (FIX g))
⇾ g (g (g (FIX g))) ⇾ ...

Forever? Normal order reduces outer most first.

Factorial

FSTEP: (λf n.IS-ZERO? n ONE
(MULTIPLY n (f (PREDECESSOR n))))

FACTORIAL ≝ FIX FSTEP

Factorial Example

FACTORIAL ONE ≝ FIX FSTEP ONE

  • FIX FSTEP ONE ⇾ FSTEP (FIX FSTEP) ONE
  • IS-ZERO? ONE ONE (MULTIPLY ...)
  • MULTIPLY ONE (FIX FSTEP (PREDECESSOR ONE))
  • MULTIPLY ONE (FIX FSTEP ZERO)
  • MULTIPLY ONE (FSTEP (FIX FSTEP) ZERO)
  • MULTIPLY ONE (IS-ZERO? ZERO ONE (...))
  • MULTIPLY ONE ONE ⇾ ONE

Factorial: Demo

Factorial Steps

FactorialResultSteps
0!19
1!130
2!2127
3!6646
4!243,873
5!12026,899
.........

Division

Let's revisit division:

DIVIDE: λn.FIX (λc n m f a.(λd.IS-ZERO? d a
(f (c d m f a))) (SUBTRACT n m)) (SUCCESSOR n)

This is just repeated subtraction.

Division: Demo

Ordered Pairs

We can take advantage of closure to store values.

  • PAIR: λh t f.f h t
  • HEAD: λp.p TRUE
  • TAIL: λp.p FALSE

This will serve as a foundation for data structures.

Ordered Pairs

How does this work in practice?

  • PAIR FIRST LAST ⇾ (λh t f.f h t) FIRST LAST
    (λb f.f FIRST t) LAST
    ⇾ λf.f FIRST LAST
  • HEAD (PAIR FIRST LAST)
    (λp.p TRUE) λf.f FIRST LAST

    (λf.f FIRST LAST) TRUE

    TRUE FIRST LAST
    ⇾ FIRST
  • TAIL (PAIR FIRST LAST) ⇾ ... ⇾
    FALSE FIRST LAST
    ⇾ LAST

Lists

Chaining ordered pairs together to make lists requires a way to find the end.

  • IS-NIL?: λp.p λh t.FALSE
  • NIL: λf.TRUE

Lists

IS-NIL? NIL

  • (λp.p λh t.FALSE) λf.TRUE
  • (λf.TRUE) λh t.FALSE ⇾ TRUE

NIL acts like a pair but ignores the function

Lists

IS-NIL? (PAIR FIRST LAST)

  • (λp.p λh t.FALSE) (PAIR FIRST LAST)
  • (PAIR FIRST LAST) λh t.FALSE
  • (λf.f FIRST LAST) λh t.FALSE
  • (λh t.FALSE) FIRST LAST ⇾ FALSE

Pairs are not NIL regardless of contents

Lists

Now we can construct lists of arbitrary length.

  • PAIR ONE NIL
  • PAIR ONE (PAIR TWO NIL)
  • PAIR ONE (PAIR TWO (PAIR THREE NIL))
  • ...

So how do we work with these?

Select

We can traverse a list using Church numerals.

NTH: λn l.n (λl.IS-NIL? l NIL (TAIL l)) l

  • NTH ONE (PAIR a (PAIR b (PAIR c NIL)))
  • ⇾ ONE (λ l.IS-NIL? l NIL (TAIL l)) (PAIR ...)
  • ⇾ (λ l.IS-NIL? l NIL (TAIL l)) (PAIR ...)
  • ⇾ IS-NIL? (PAIR ...) NIL (TAIL (PAIR ...))
  • ⇾ TAIL (PAIR a (PAIR b (PAIR c NIL)))
  • ⇾ PAIR b (PAIR c NIL)

An index beyond the end of the list will give NIL

Count

How many elements are in our list?

  • COUNT ≝ FIX (λc l.IS-NIL? l ZERO
    (ADD ONE (c (TAIL l)))
  • COUNT (PAIR ONE (PAIR TWO
    (PAIR THREE NIL)))
  • ADD ONE (ADD ONE (ADD ONE ZERO))
  • THREE

Count: Demo

Sums

Let's add up a list of numbers.

  • SUM ≝ FIX (λf l.IS-NIL? l ZERO
    (ADD (HEAD l) (f (TAIL l))))
  • SUM (PAIR ONE (PAIR TWO (PAIR THREE NIL)))
  • ADD ONE (ADD TWO (ADD THREE ZERO)) ⇾ SIX

Sums: Demo

Average

Almost too easy:

AVERAGE ≝ (λl.DIVIDE (SUM l) (COUNT l))

This will round down to the nearest natural number, which is not ideal.

Map

We can apply a function to every member of a list

  • MAP ≝ FIX (λf g l.IS-NIL? l NIL
    (PAIR (g (HEAD l)) (f g (TAIL l))))
  • MAP (λn.ADD n ONE)
    (PAIR ONE (PAIR TWO (PAIR THREE NIL)))
  • PAIR TWO (PAIR THREE (PAIR FOUR NIL))

Map: Demo

Review: Data Structures

  • PAIR: λa b f.f a b
  • HEAD: λp.p TRUE
  • TAIL: λp.p FALSE
  • NIL: λf.TRUE
  • IS-NIL?: λp.p λh t.FALSE

Integers

How can we construct an integer? Consider a pair of Church numerals. If we interpret them as a subtraction we can represent any integer value.

INT-CREATE: PAIR n m

This represents the integer n - m

Integers

Here are some simple tools for integers.

  • INT-IS-ZERO?: λn.EQUAL? (HEAD n) (TAIL n)
  • INT-EQUAL?: λn m.EQUAL?
    (ADD (HEAD n) (TAIL m)) (ADD (HEAD m) (TAIL n))
  • INT-NEGATE: λn.PAIR (TAIL n) (HEAD n)

Integers: Arithmetic

Integer arithmetic is messy but straightforward.

  • INT-ADD: λn m.PAIR (ADD (HEAD n) (HEAD m))
    (ADD (TAIL n) (TAIL m))
  • INT-MULTIPLY: λn m.PAIR
    (ADD (MULTIPLY (HEAD n) (HEAD m))
    (MULTIPLY (TAIL n) (TAIL m)))
    (ADD (MULTIPLY (HEAD n) (TAIL m))
    (MULTIPLY (TAIL n) (HEAD m)))

Rationals

How can we represent rational numbers? Consider an pair of integers. If we interpret this as a division we can represent any rational number.

RATIONAL-CREATE: PAIR a b

This represents the rational number a / b

Rationals

Here are some simple tools for rational numbers.

  • RATIONAL-IS-ZERO?: λa.INT-IS-ZERO? (HEAD a)
  • RATIONAL-EQUAL?: λa b.INT-EQUAL?
    (INT-MULTIPLY (HEAD a) (TAIL b))
    (INT-MULTIPLY (HEAD b) (TAIL a))
  • RATIONAL-NEGATE: λa.PAIR
    (INT-NEGATE (HEAD a)) (TAIL a)
  • RATIONAL-INVERSE: λa.PAIR (TAIL a) (HEAD a)

Rationals: Arithmetic

And of course we can add and multiply.

  • RATIONAL-ADD: λn m.PAIR
    (INT-ADD (INT-MULTIPLY (HEAD n) (TAIL m))
    (INT-MULTIPLY (HEAD m) (TAIL n)))
    (INT-MULTIPLY (TAIL n) (TAIL m))
  • RATIONAL-MULTIPLY: λn m.PAIR
    (INT-MULTIPLY (HEAD m) (TAIL n)))
    (INT-MULTIPLY (TAIL n) (TAIL m))

Lambda Computation

  • Boolean Logic: λa b.a, λa b.b
  • Numbers: λf a.a, λn f a.f (n f a)
  • Arithmetic: λm n.n (λn f a.f (n f a)) m
  • Flow Control: λf.(λa.f (a a)) λa.f (a a)
  • Data Structures: λh t f.f h t

Lambda calculus simple, compact and universal.

Starling and Kestral

The Starling and Kestral combinators are universal (Schönfinkel 1924 CE). Like lambda calculus and Turing machines they can compute any computable function.

  • Starling (S): λa b c.a c (b c)
  • Kestral (K): λa b.a

Combinators

A combinator is an abstraction that has no free variables. As a consequence they have no state or external interactions.

Combinator Birds

Raymond Smullyan wrote a book about combinatory logic in 1985 CE titled To Mock a Mockingbird. Giving names to combinators helps us to remember them.

Meet the Birds

  • Ibis (Identity): λa.a
  • Mockingbird (Self-Apply): λa.a a
  • Kestral (True/Const): λa b.a
  • Kite (False/Zero): λa b.b
  • Cardinal (Flip/Not): λa b c.a c b
  • Bluebird (Compose): λa b c.a (b c)
  • Thrush (Power): λa b.b a
  • Vireo (Pair): λa b c.c a b
  • Starling (Fusion): λa b c.a c (b c)

S K: Identity

The identity has many Starling and Kestral forms:

  • S K X ≑ (λa b c.a c (b c)) K X
  • ⇾ (λb c.K c (b c)) X
  • ⇾ λc.K c (X c) ≑ λc.(λa b.a) c (X c)
  • ⇾ λc.(λb.c) (X c) ⇾ λc.c ≑ I

S K K ≑ S K S ≑ S K (anything) ≑ I

S K: Kestral

A common pattern reduces to the Kestral:

  • S (K K) I ≑ (λa b c.a c (b c)) (K K) I
  • ⇾ (λb c.(K K) c (b c)) I
  • ⇾ (λc.(K K) c (I c)) ≑ (λc.(λa b.a) K c ((λa.a) c))
  • ⇾ (λc.(λb.K) c ((λa.a) c)) ⇾ λc.K ((λa.a) c)
  • ⇾ (λc.K c) ≑ λc.(λa b.a) c
  • ⇾ λc.λb.c ≑ λc b.c ≑ K

S (K K) I ≑ K

S K: Kite

The Kite is the Kestral followed by Identity:

  • K I ≑ (λa b.a) λa.a
  • ⇾ λb.λa.a ≑ λb a.a ≑ Ki

S K: Mockingbird

Mockingbird is easy to make with Starling and Ibis

  • S I I ≑ (λa b c.a c (b c)) (λa.a) (λa.a)
  • ⇾ (λb c.(λa.a) c (b c)) (λa.a)
  • ⇾ λc.(λa.a) c ((λa.a) c)
  • ⇾ λc.c ((λa.a) c) ⇾ λc.c c ≑ M

M ≑ S I I

S K: Bluebird

Bluebird is easy to make with Starling and Ibis

  • S (K S) K ≑ (λa b c.a c (b c)) (K S) K
  • ⇾ (λb c.(K S) c (b c)) K ⇾ λc.K S c (K c)
  • ⇾ λc.(λa b.a) S c (K c)
  • ⇾ λc.(λb.S) c (K c) ⇾ λc.S (K c)
  • ⇾ λc.(λa b d.a d (b d)) (K c)
  • ⇾ λc b d.K c d (b d) ⇾ λc b d.c (b d)
  • λc b d.c (b d) ≑ λa b c.a (b c) ≑ B

B ≑ S (K S) K

Converting Lambda

Any lambda term can be converted to S K:

  • T[a] ⟹ a ∧ T[(E)] ⟹ (T[E]) ∧ T[E F] ⟹ T[E] T[F]
  • T[λa.E] ∧ a∉FV[E] ⟹ K T[E]
  • T[λa.a] ⟹ S K K ⟹ I
  • T[λa.λb.E] ∧ a∈FV[E] ⟹ T[λa.T[λb.E]]
  • T[λa.E F] ∧ a∈(FV[E] ∪ FV[F]) ⟹
    S T[λa.E] T[λa.F]]

These rules cover all cases (Source: Wikipedia)

Conversion: Thrush

Thrush: λa b.b a

  • T[λa b.b a] ⟹ T[λa.T[λb.b a]]
  • T[λa.S T[λb.b] T[λb.a]] ⟹ T[λa.S I (K a)]
  • S T[λa.(S I)] T[λa.K a]
  • S (K (S I)) (S T[λa.K] T[λa.a])
  • S (K (S I)) (S (K K) I) S (K (S I)) K

S K Combinators

  • Ibis: I = S K K = S K S
  • Kite: Ki = K I
  • Mockingbird: M = S I I
  • Bluebird: B = S (K S) K
  • Thrush: Th = S (K (S I)) K
  • Cardinal: C = S (S (K (S (K S) K)) S) (K K)
  • Vireo: V = S (K (S (S (K (S (K S) K)) S) (K K)))
    (S (K (S I)) K)
  • FIX = S (K (S I I)) (S (S (K S) K) (K (S I I)))

S K: Convert Anything

There Can Be Only One

Chris Barker 2001 CE: iota combinator is universal

i ≝ λf.f (λa b c.a c (b c)) λd e.d

  • i i = (λf.f S K) λf.f S K ⇾ (λf.f S K) S K
    S S K K
    ⇾ S K (K K) ⇾ I
  • i (i (i i)) = (λf.f S K) ((λf.f S K) I) ⇾ (λf.f S K) I S K
    I S K S K
    ⇾ S K S K ⇾ I K ⇾ K
  • i (i (i (i i))) = (λf.f S K) K ⇾ K S K ⇾ S

Any combinator can be a bewildering set of iotas

Binary Iota

Chris Barker created a simple binary encoding:

term := "1" | "0" term term

  • 1 = i
  • 011 = i i = I
  • 0101011 = i (i (i i)) = K
  • 010101011 = i (i (i (i i))) = S

But remember... just because you can stick peas up your nose doesn't mean you should.

Conclusion

None of this is efficient compared to conventional von Neumann programming languages.

What's the point?

Studying Computation

Formal systems can be easier to study than those intended for practical use. For example, some problems are not decidable which means no algorithm can resolve them for all possible inputs.

Example: the Halting Problem. No algorithm can determine whether any possible computation halts in a finite number of steps.

Let's get Functional

As we've seen, expressions are universal, stateless (therefore can be computed in parallel).

This makes them a powerful foundation for functional programming languages.

Control the Computation

Running untrusted code requires limiting cycle consumption and memory. How would you do that with a conventional programming language?

It's obvious how to do this with lambda calculus: limit reductions and expression size.

Conclusion

Computation is powerful and can bloom from simple seeds.