This is an old revision of the document!
Lab 7. Lambda Calculus. Intro to Haskell
7.1 Lambda Calculus
Lambda Calculus represent a axiomatic system that can be used for very basic proofs.
Given a set of variables VARS, a expression under lambda calculus can be:
variable | $ x $ | $ x \in VARS $ |
function | $ \lambda x.e $ | $ x \in VARS $, $ e $ is a $ \lambda $-expression |
application | $ (e_1 \ e_2) $ | $ e_1, e_2 $ are $ \lambda $-expressions |
To evaluate $\lambda$-expressions, there are two types of reduction operations:
- $\alpha$-conversion: given a expression: $ \lambda x.e $, you can rename all occurences of x in e with y (used for avoiding name collisions).
- $\beta$-reduction: given a expression: $ \lambda x.body \ param$, you can replace all occurences of x in body with param (We will denote this action with: $ body[x \ / \ param] $).
If a expression cannot be reduced further using $ \beta $-reductions, we say the expression is in $ \beta $-normal form.
Free and bound variables
Take the following Scala snippet as an example:
def f(x: Int) = x + y
We can say that the second occurence of $ x $ is bounded by the $ x $ that appears as a function parameter. When we call the function, the occurence of $ x $ is replaced by the argument that was provided to $ f $. In contrast, $ y $ is a free variable.
This code might look weird, where does $ y $ come from? What does it do? Why would we use a variable that we don't instantiate (i.e. is not bound to anything)? Well, the snippet actually comes from a broader context:
def g(x: Int, y: Int) = { def f(x: Int) = x + y f(x * y) }
In this new snippet we can see that all variables are bounded, and the free variable from before is bounded by the outer function, but only the free variable, notice that $ x $ is still bounded by the inner function, and the $ x $ parameter of $ g $ is ignored inside $ f $.
The importance of free variables is that only free occurences of a sub-expression can be bounded by the outer expression.
Translating to lambda calculus, when reducing $ \lambda x.e_1 \ e_2 $ to $ e_1[x \ / \ e_2] $, only free occurences of $ x $ in $ e_1 $ will be replaced by $ e_2 $.
More generally, we say that:
- if all occurences of a variable in a expressions are bounded , the variable is said to be bounded
- if one occurence of a variable in a expressions is free , the variable is said to be free
Exercise
7.1.1. For every variable occurence, mention if it's a free or a bounded occurence:
- $ \lambda y.(\lambda x.x \ (x \ y)) $
- $ \lambda x.(x \ \lambda y.(x \ y \ z)) \ (x \ \lambda y.x) $
- $ \lambda f.((\lambda x.(f \ (x \ x))) \ (\lambda x.(f \ (x \ x)))) $
Reduction rules
Using what we learned from free and bounded variables, we can define a algorithm for $\beta$-reduction, given a expression $ e_1[x \ / \ e_2] $:
$ e_1 $ | $ e_1[x \ / \ e_2] $ | condition | |
---|---|---|---|
$ x $ | $ e_2 $ | ||
$ y $ | $ y $ | $ x \neq y $ | |
$ E_1 \ E_2 $ | $ E_1[x \ / \ e_2] \ E_2[x \ / \ e_2] $ | ||
$ \lambda x.e $ | $ \lambda x.e $ | ||
$ \lambda y.e $ | $ \lambda y.e[x \ / \ e_2] $ | $ x \neq y $, $ y $ does not appear free in $ e_2 $ | |
$ \lambda y.e $ | $ \{\lambda z.e[y \ / \ z]\}[x \ / \ e_2] $ | $ x \neq y $, appears free in $ e_2 $ | ( $ z $ is a new variable that is not free in $ e $ or $ e_2 $ ) |
Evaluation order
Q: If we have multiple redexes in a expression, which one do we evaluate?
A: We can evaluate any of them, and it is guaranteed by Church-Rosser theorem that if the expression is reducible, we will eventually get the same $ \beta $-normal form.
To not just randomly choose redexes, there exist reduction strategies , from which we will use the Normal Order and Applicative Order:
- Normal Order evaluation consist of always reducing the leftmost, outermost redex (whenever possible, subsitute the arguments into the function body)
- Applicative Order evaluation consist of always reducing the leftmost, innermost redex (always reduce the function argument before the function itself)
Exercise
7.1.2. Evaluate in both Normal Order and Applicative Order the following expressions:
- $ \lambda x.\lambda y.(x \ y \ x) \ \lambda x.\lambda y.x \ (\lambda x.\lambda y.\lambda z.(x \ z \ y) \ \lambda x.\lambda y.y)$
- $ \lambda x.y \ (\lambda x.(x \ x) \ \lambda x.(x \ x))$
Booleans
We can encode boolean values TRUE and FALSE in lambda calculus as functions that take 2 values, x and y, and return the first (for TRUE) or second (for FALSE) value.
$ TRUE = \lambda x.\lambda y.y$
$ FALSE = \lambda x.\lambda y.x$
Natural Numbers
Church numerals represent natural numbers as higher-order functions. Under this representation, the number n is a function that maps f to its n-fold composition.
$ N0 = \lambda f.\lambda x. x $
$ N1 = \lambda f.\lambda x. (f \ x) $
$ N2 = \lambda f.\lambda x. (f \ (f \ x)) $
…
You can also define operation on church numerals, some (that were discussed during the lecture) are:
$ SUCC = \lambda n.\lambda f.\lambda x.(f \ ((n \ f) \ x)) $
$ ISZERO = \lambda n.((n \lambda x.FALSE) \ TRUE) $
$ ADD = \lambda n.\lambda m.\lambda f.\lambda x.((n \ f) ((m \ f) \ x)) $
7.1.1 Define multiplication under church numerals: $ MULT = \lambda n.\lambda m. \ \ldots $ (without using the Y-combinator)
7.1.2 Define exponentiation under church numerals: $ EXP = \lambda n.\lambda m. \ \ldots $
7.1.3 (*) Define the predecessor operator, that takes a number and returns the number prior to it.
What's the predecessor of 0? Evaluate $ PRED \ N0 $.
7.1.4 Define substraction under church numerals: $ SUB = \lambda n.\lambda m. \ \ldots $ (Hint: use $ PRED $).
What happens if you try to substract a bigger number from a smaller one? Evaluate $ SUB \ N1 \ N2 $.
7.1.5 Define $ LEQ $ (less or equal). $ LEQ \ n \ m $ should return TRUE if $ n \leq m $ and FALSE if $ n > m $.
7.1.6 Define $ EQ $ (equality). $ EQ \ n \ m $ should return TRUE if $ n = m $ and FALSE otherwise.
7.2 Recursion and the Y-Combinator
In lambda calculus, recursion is achieved using the fixed-point combinator (or Y combinator).
A fixed-point combinator is a higher-order function that returns some fixed point of it's argument function (x is a fixed pointed for a function f if $ f(x) = x $). That means: $ f \ (fix \ f) = fix \ f $
And by repeated application: $ fix \ f = f \ (f \ (\ldots f \ (fix \ f)\ldots)) $
The Y-combinator in lambda calculus looks like this:
$ FIX = \lambda f.(\lambda x.f \ (x \ x)) (\lambda x.f \ (x \ x)) $
7.2.1 Using the Y-Combinator, define a function that computes the factorial of a number n.
7.2.2 Using the Y-Combinator, define a function $ FIB $ that computes the n-th fibonacci number.