Lambda calculus is a mathematical language for describing arbitrary expressions built from the application of functions to other expressions. It is as powerful as any other notations for describing algorithms, including any programming language.
Main operation of λ - calculus is the application of one subexpression (function) to another (its single argument) by substitution of the argument into the function's body. Currying is the normal mode of execution.
- functions have no names
- names given to things are the formal parameters of a function
- lexical scoping
{{{ := a|b|c|d|e ... := ( λ"|") := () := | | }}}
Examples:
{{{ identifier: a application: ((λx|(yx))a) function: (λx|(yx)) }}}
In λ - Calculus, what an expression means is equivalent to what it can reduce to after all function applications have been performed.
Interpretative semantic model:
- find all possible application subexpressions in the expression
- pick one where the function is neither a simple identifier nor an
application expression, but a function expression of the form
(λx|E)
whereE
is arbitrary expression - assume that the expression to the right of this function is expression
A
- perform the substitution
[A/x]E
- identify all free occcurences ofx
inE
and replace them byA
- replace the entrire application by the result of this substitution and loop back to step 1
Sample computation:
((λx|(xi))((λz|((λq|q)z))h)) # applying (λx|(xi)) to ((λz|((λq|q)z))h))
------------------------------- # ([((λz|((λq|q)z))h)/x](λx|(xi)))
(((λz|((λq|q)z))h)i) # applying (λq|q) to z ([z/q]q)
(((λz|z)h)i) # applying (λz|z) to h ([h/z]z)
(hi)
(...((AB)C)...X)
is the same as (ABC...X)
(AB)
is the same as AB
, therefore (λx|(λy|(λz|M)))
is the same as (λx|λy|λz|M)
(λx|λy|λz|M)
is the same as (λxyz|M)
An identifier is used in λ - Calculus as a placeholder to indicate where in a function's definition a real argument should be substituted, when the function is applied. Each use of the identifier in the function body is called an instance of that identifier. Scope of an identifier is the region of the expression where instances of the identifier will always have the same value - in λ - Calculus, the rules for scoping are the same as in conventional statically scoped languages. An instance can be bound or free depending on whether the identifier is in the argument list of the function. belongs to the current scope.
To clarify a little bit more:
- free variables of
x
are justx
- free variables of
(λx|A)
are free variables ofA
withx
removed - free variables of
(AB)
are union of free variables ofA
andB
Examples:
(λx|x) #has no free variables
(λx|xy) #has one free variable - y
(λx|xx)x #has one free variable - x !but only last instance!
An expression of the form (λx|E)A
, where E
and A
are arbitrary
expressions, the evaluation of this expression involves the substitution
of the expression A
for all appropriate free instances of the identifier
x
in the expression E
. Any bound instances of x
and any other instances
are left unchanged.
Consider an expression (λx|(λy|B))A
. If A
contains any free occurences of
y
, then a blind substitution into the body of the function will end up
changing those instances of y
in A
from free to bound, radically changing
the value of the expression. The solution is to change the formal parameter of
the function and all free occurences of that symbol in the function's body.
We replace all y
in B by z
(or any not yet used name [z/y]B
)
Two expressions A
and B
are the same if there is a formal way of converting
from to other via a series of reductions (A >> B
means A
reduces to B
)
- alpha conversion corresponds to simple and safe renaming of formal identifiers. Two expressions are the same if they differ only in symbol names.
- beta conversion matches normal λ - Calculus function applications. Two expressions are the same if one represents the result of performing some function application found in the other.
- eta conversion corresponds to a simple optimization of a function
application that occurs quite often, it is basically a special case of the
beta conversion:
(λx|Ex)A >> EA
When we can no longer apply beta or eta conversions, the expression is in normal order
In 1936 Alonzo Church and J. R. Rosser proved two important theorems.
Church-Rosses Theorem I states that if an expression A
through two
different conversion sequences, can reduce to two different expressions B
and
C
, there is always some other expression D
such that both B
and C
can
reduce to it.
Church-Rosses Theorem II states that if an expression A
reduces to B
,
and B
is in normal order (therefore when A
can be reduced to normal order),
then we can get from A
to B
by doing the leftmost reduction at each
step. This one is important, since it gives us a concrete algorithm for finding
normal order of the expression.
Normal-order reduction follows CRT - it always locates the leftmost function involved in the application and substitues unchanged copies of arguments into the function body. No reductions are performed on the arguments until after.
Applicative-order reduction reduces the argument completely before function is applied on it.
Normal-order reduction terminates with normal-order expression, but possibly by evaluating the same expression multiple times. Applicative-order reduction is not guaranteed to stop when doing only leftmost application.
Example of normal order reduction:
(λx|(λw|(λy|wyw)b))((λx|xxx)(λx|xxx))((λz|z)a)
(λw|(λy|wyw)b)((λz|z)a)
(λy|((λz|z)a)y((λz|z)a))b
((λz|z)a)b((λz|z)a) #first redundant application
(ab((λz|z)a)) #second redundant application
(aba)
Example of applicative order reduction:
(λx|(λw|(λy|wyw)b))((λx|xxx)(λx|xxx))((λz|z)a)
#evaluation of ((λx|xxx)(λx|xxx))
([(λx|xxx)/x](xxx))
((λx|xxx)(λx|xxx)(λx|xxx)) #and will aparently continue forewer
Mathematically, we have to define how integer 0 will look like, and then the
function s
which given an integer k
will produce an expression for the
integer k+1
.
Because we are using λ - Calculus, the integer 0 will be represented as a function:
0 = (λsz|z)
The definition of the successor function is:
s(x) = (λxyz|y(xyz))
Some examples to hurt my brain more:
0 = (λsz|z)
1 = (λxyz|y(xyz))(λsz|z)
(λyz|y((λsz|z)yz))
(λyz|yz)
2 = (λxyz|y(xyz))(λsz|sz)
(λyz|y((λsz|sz)yz))
(λyz|y(yz))
3 = (λxyz|y(xyz))(λsz|s(sz))
(λyz|y((λsz|s(sz))yz))
(λyz|y(y(yz)))
...
Addition:
(λwzyx|wy(zyx))
Multiplication
(λwzy|w(zy))
Example 2 + 1:
(λwzyx|wy(zyx))(λsz|s(sz))(λsz|sz)
(λyx|(λsz|s(sz))y((λsz|sz)yx))
(λyx|y(y((λsz|sz)yx)))
(λyx|y(y(yx)))
3
Example 1 * 2:
(λwzy|w(zy))(λsz|sz)(λsz|s(sz))
(λy|(λsz|sz)((λsz|s(sz))y))
(λy|(λsz|sz)(λz|y(yz)))
(λy|(λz|(λz|y(yz))z))
(λy|(λz|y(yz)))
2
Example 2 * 3
(λwzy|w(zy))(λsz|s(sz))(λsz|s(s(sz)))
(λy|(λsz|s(sz))((λsz|s(s(sz)))y))
(λy|(λsz|s(sz))(λz|y(y(yz))))
(λy|(λz|(λz|y(y(yz)))((λz|y(y(yz)))z)))
(λy|(λz|(λz|y(y(yz)))(y(y(yz)))))
(λy|(λz|y(y(y(y(y(yz)))))))
6 #uff :)
true = T = (λxy|x)
false = F = (λxy|y)
On contrary to integers, where the internal body matched our concepts of
integers, these functions are used because of the way how they function when
given real arguments. Consider Q
and R
are arbitrary expressions:
if P == T then PQR = T Q R = (λxy|x)QR = Q
if P == F then PQR = F Q R = (λxy|y)QR = R
Other interesting functions:
not = (λw|wFT)
and = (λwz|wzF)
or = (λwz|wTz)
xor = (λwz|w(zFT)(zTF))
zero(x) = (λx|x F not F)
Examples:
not T = (λw|w(λxy|y)(λxy|x))(λxy|x)
(λxy|x)(λxy|y)(λxy|x)
(λxy|y)
F
and T F = (λwz|wzF)(λxy|x)(λxy|y)
((λxy|x)(λxy|y)F)
(λxy|y)
F
zero(1) = (λx|x (λxy|y) (λw|w(λxy|y)(λxy|x)) (λxy|y))(λsz|sz)
((λsz|sz) (λxy|y) (λw|w(λxy|y)(λxy|x)) (λxy|y))
((λxy|y) (λw|w(λxy|y)(λxy|x)) (λxy|y))
(λxy|y)
F
zero(0) = (λx|x F not F)(λsz|z)
((λsz|z) F not F)
(not F)
T
Consider the application RA
, where R
is some recursively defined function
and A
is some argument expression. If A
satisfies the basis test for
R
, then RA
reduces to basis case. If not, then RA
reduces to some
other expression of the form ...(RB)...
where B
is some simpler expression.
Making R
recursive has a lot to do with making it repeat itself.
How to do this self-repetition - try to evaluate following expression:
(λx|xx)(λx|xx)
The expression has the property that it does not change regardless of how many
beta conversions are performed. Using this as a basis, for any lambda
expression R
:
((λx|R(xx))(λx|R(xx)))
R((λx|R(xx))(λx|R(xx)))
R(R((λx|R(xx))(λx|R(xx))))
R(R(R((λx|R(xx))(λx|R(xx)))))
This allows us to compose an arbitrary function R
on itself an infinite
number of times. So to finally tell you the secret - here comes the Y
combinator (also called fixed point combinator):
(λy|((λx|y(xx))(λx|(xx))))
What a recursion would it be without the factorial:
fact(n) = if zero(n) then 1 else n*fact(n-1)
λ - Calculus equivalent:
fact(n) = Y(λfn|zero n 1 (* n (f (- n 1))))
fact(4):
R = (λrn|zero n 1 (* n (r (- n 1))))
fact(4)
Y R 4
R (YR) 4
(λn|zero n 1 (* n (YR (- n 1))))4
zero 4 1 (* 4 (YR (- 4 1)))
zero 4 1 (* 4 ((YR) 3))
F 1 (* 4 ((YR) 3))
(* 4 ((YR) 3))
(* 4 (R (YR) 3))
(* 4 ((λn|zero n 1 (* n (YR (- n 1))))3))
(* 4 (zero 3 1 (* 3 (YR (- n 1)))))
(* 4 (* 3 ((YR) 2)))
(* 4 (* 3 (R (YR) 2)))
(* 4 (* 3 ((λn|zero n 1 (* n (YR (- n 1))))2)))
(* 4 (* 3 (zero 2 1 (* 2 (YR (- 2 1))))))
(* 4 (* 3 (* 2 (R (YR) 1))))
...
(* 4 (* 3 (* 2 (* 1 1))))
...
24