Lambda Calculus: Understanding Functions, Composition, and Currying - Prof. Carlos A. Vare, Study notes of Programming Languages

Explore the foundations of functional programming languages through lambda calculus. Learn about defining functions, traditional function composition, lambda expressions, and currying. Discover the differences between applicative and normal order evaluation and the importance of free and bound variables.

Typology: Study notes

Pre 2010

Uploaded on 08/09/2009

koofers-user-7m4
koofers-user-7m4 🇺🇸

10 documents

1 / 7

Toggle sidebar

This page cannot be seen from the preview

Don't miss anything!

bg1
CSCI.4430/6969 Programming Languages
Lecture Notes on the Lambda Calculus
Carlos A. Varela
September 17, 2007
The lambda calculus created by Church and Kleene in the 1930’s is at the heart of functional program-
ming languages. It is Turing-complete, that is, any computable function can be expressed and evaluated
using the calculus. It is useful to study programming language concepts because of its high level of ab-
straction. We will briefly motivate the calculus and introduce its syntax and semantics.
The mathematical notation for defining a function is with a statement such as
f(x) = x2, f :ZZ,
where Zis the set of all integers. The first Zis called the domain of the function, or the set of values x
can take. The second Zis called the range of the function, or the set containing all possible values of f(x).
Suppose f(x) = x2and g(x) = x+ 1. Traditional function composition is defined as
fg=f(g(x)) .
With our functions fand g,
fg=f(g(x)) = f(x+ 1) = x2+ 2x+ 1.
Similarly
gf=g(f(x)) = g(x2) = x2+ 1.
Therefore, function composition is not commutative.
In lambda (λ) calculus, we can use a different notation to define these same concepts. To define a
function f(x) = x2, we instead write
λx.x2.
Similarly for g(x) = x+ 1 we instead write
λx.x + 1.
To describe a function application such as f(2) = 4, we write
(λx.x22) 224.
The syntax for lambda calculus expressions is
e::= v variable
|λv.e lambda expression
|(e e) procedure call
The semantics of the lambda calculus, or the way of evaluating or simplifying expressions, is defined
by the rule
(λx.E M)E{M/x}.
1
pf3
pf4
pf5

Partial preview of the text

Download Lambda Calculus: Understanding Functions, Composition, and Currying - Prof. Carlos A. Vare and more Study notes Programming Languages in PDF only on Docsity!

CSCI.4430/6969 Programming Languages

Lecture Notes on the Lambda Calculus

Carlos A. Varela

September 17, 2007

The lambda calculus created by Church and Kleene in the 1930’s is at the heart of functional program- ming languages. It is Turing-complete, that is, any computable function can be expressed and evaluated using the calculus. It is useful to study programming language concepts because of its high level of ab- straction. We will briefly motivate the calculus and introduce its syntax and semantics. The mathematical notation for defining a function is with a statement such as

f (x) = x^2 , f : Z → Z,

where Z is the set of all integers. The first Z is called the domain of the function, or the set of values x can take. The second Z is called the range of the function, or the set containing all possible values of f (x). Suppose f (x) = x^2 and g(x) = x + 1. Traditional function composition is defined as

f ◦ g = f (g(x)).

With our functions f and g,

f ◦ g = f (g(x)) = f (x + 1) = x^2 + 2x + 1.

Similarly g ◦ f = g (f (x)) = g(x^2 ) = x^2 + 1. Therefore, function composition is not commutative. In lambda (λ) calculus, we can use a different notation to define these same concepts. To define a function f (x) = x^2 , we instead write λx.x^2.

Similarly for g(x) = x + 1 we instead write λx.x + 1.

To describe a function application such as f (2) = 4, we write

(λx.x^2 2) ⇒ 22 ⇒ 4.

The syntax for lambda calculus expressions is

e ::= v – variable | λv.e – lambda expression | (e e) – procedure call

The semantics of the lambda calculus, or the way of evaluating or simplifying expressions, is defined by the rule (λx.E M ) ⇒ E{M/x}.

The new expression E{M/x} can be read as “replace ‘fresh’ x’s in E with M ”. Informally, a “fresh” x is an x that is not nested inside another lambda expression. We will cover free and bound variable occurrences in detail later on. For example, in the expression (λx.x^2 2),

E = x^2 and M = 2. To evaluate the expression, we replace x’s in E with M , to obtain

(λx.x^2 2) ⇒ 22 ⇒ 4.

In lambda calculus, all functions may only have one variable. Functions with more than one variable may be expressed as a function of one variable through currying. Suppose we have a function of two variables expressed in the normal way

h(x, y) = x + y, h : (Z × Z) → Z.

With currying, we can input one variable at a time into separate functions. The first function will take the first argument, x, and return a function that will take the second variable, y, and will in turn provide the desired output. To create the same function with currying, let

f : Z → (Z → Z)

and g : Z → Z.

That is, f maps integers to a function, and g maps integers to integers. The function f (x) returns a function gx that provides the appropriate result when supplied with y. For example,

f (2) = g 2 , where g 2 (y) = 2 + y.

So f (2)(3) = g 2 (3) = 2 + 3 = 5.

In lambda calculus this function would be described with currying by

λx.λy.x + y.

For function application, we nest two application expressions

((λx.λy.x + y 2) 3).

We may then simplify this expression using the semantic rule (also called beta (β) reduction)

((λx.λy.x + y 2) 3) ⇒ (λy.2 + y 3) ⇒ 2 + 3 ⇒ 5.

The composition operation ◦ can itself be considered a function (also called higher-order function) that takes two other functions as its input and returns a function as its output; that is if the first function is Z → Z and the second function is also Z → Z, then

◦ : (Z → Z) × (Z → Z) → (Z → Z).

We can also define function composition in lambda calculus. Suppose we want to compose the square function and the increment function, defined as

λx.x^2 and λx.x + 1.

We can define function composition as a function itself with currying by

λf.λg.λx.(f (g x)).

However, things get more complicated. It is possible when performing β -reduction to inadvertently change a free variable into a bound variable, which changes the meaning of the expression. In the statement

(λx.λy.(x y) (y w)),

the second y is bound to λy and the final y is free. Taking E = λy.(x y) and M = (y w), we could mistakenly arrive at the simplified expression

λy.((y w) y).

But now both the second and third y’s are bound, because they are both a part of of the λy function definition. This is wrong because one of the y’s should remain free as it was in the original expression. To get around this, we can change the λy expression to a λz expression

(λx.λz.(x z) (y w)),

which again does not change the meaning of the expression. This process is called α -renaming. Now when we perform the β -reduction, the original two y variables are not confused. The result is

λz.((y w) z).

Here, the free y remains free.

2 Order of Evaluation

There are different ways to evaluate lambda expressions. The first method is to always fully evaluate the arguments of a function before evaluating the function itself. This order is called applicative order. In the expression (λx.x^2 (λx.x + 1 2)),

the argument (λx.x + 1 2) should be simplified first. The result is

⇒ (λx.x^2 2 + 1) ⇒ (λx.x^2 3) ⇒ 32 ⇒ 9.

Another method is to evaluate the left-most redex first. A redex is an expression of the form (λx.E M ), on which β -reduction can be performed. This order is called normal order. The same expression would be reduced from the outside in, with E = x^2 and M = (λx.x + 1 2). In this case the result is

⇒ (λx.x + 1 2)^2 ⇒ (2 + 1)^2 ⇒ 9. As you can see, both orders produced the same result. But is this always the case? It turns out that the answer is “no” for certain expressions whose simplification does not terminate. Consider the expression

(λx.(x x) λx.(x x)).

It is easy to see that reducing this expression gives the same expression back, creating an infinite loop. If we consider the expanded expression

(λx.y (λx.(x x) λx.(x x))),

we find that the two evaluation orders are not equivalent. Using applicative order, the (λx.(x x) λx.(x x)) expression must be evaluated first, but this never terminates. If we use normal order, however, we evaluate the entire expression first, with E = y and M = (λx.(x x) λx.(x x)). Since there are no x’s in E to replace, the result is simply y. It turns out that it is only in these particular non-terminating cases that the two orders may give different results. The Church-Rosser theorem (also called the confluence property or the diamond property) states that if a lambda calculus expression can be evaluated in two different ways and both ways terminate, both ways will yield the same result. Also, if there is a way for an expression to terminate, using normal order will cause the termination. In other words, normal order is the best if you want to avoid infinite loops. Take as another example the C program

int loop() { return loop(); }

int f(int x, int y) { return x; }

int main() { return f(3, loop()); }

In this case, using applicative order will cause the program to hang, because the second argument loop() will be evaluated. Using normal order will terminate because the unneeded y variable will never be evaluated. Though normal order is better in this respect, applicative order is the one used by most programming languages. Why? Consider the function f (x) = x + x. To find f (4/2) using normal order, we hold off on evaluating the argument until after placing the argument in the function, so it yields

f (4/2) = 4/2 + 4/2 = 2 + 2 = 4,

and the division needs to be done twice. If we use applicative order, we get

f (4/2) = f (2) = 2 + 2 = 4,

which only requires one division. Since applicative order avoids repetitive computations, it is the preferred method of evaluation in most programming languages, where short execution time is critical.

3 Combinators

Any lambda calculus expression with no free variables is called a combinator. Because the meaning of a lambda expression is dependent only on the bindings of its free variables, combinators always have the same meaning independently of the context in which they are used. There are certain combinators that are very useful in lambda calculus: The identity combinator is defined as I = λx.x.

It simply returns whatever is given to it. For example

(I 5) ⇒ (λx.x 5) ⇒ 5.

The identity combinator in Oz can be written:

declare I = fun {$ X} X end

Contrast it to, for example, a Circumference function:

declare Circumference = fun {$ Radius} 2PIRadius end

The semantics of the Circumference function depends on the definitions of PI and *. It is, therefore, not a combinator. The application combinator is App = λf.λx.(f x),

declare Curry = fun {$ F} fun {$ X} fun {$ Y} {F X Y} end end end

Exercises

  1. α-convert the outer-most x to y in the following λ-calculus expressions, if possible:

(a) λx.(λx.x x) (b) λx.(λx.x y)

  1. β-reduce the following λ-calculus expressions, if possible:

(a) (λx.λy.(x y) (y w)) (b) (λx.(x x) λx.(x x))

  1. Install Mozart/Oz in your computer.
  2. Define functions Plus, PlusC (its curried version), and test them.
  3. † Write a function composition combinator in the λ-calculus.
  4. † Define a curried version of Compose in Oz, ComposeC, without using the Curry combinator. (Hint: It should look very similar to the λ-calculus expression from the previous exercise.)