cits 3242 programming paradigms · 2011. 6. 12. · cits 3242 programming paradigms part iv:...

16
CITS 3242 Programming Paradigms Part IV: Advanced Topics Topic 21: Lambda Calculus 1 This topic briefly introduces the logical foundation of functional programming: the λ-calculus.

Upload: others

Post on 27-Aug-2020

3 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: CITS 3242 Programming Paradigms · 2011. 6. 12. · CITS 3242 Programming Paradigms Part IV: Advanced Topics Topic 21: Lambda Calculus 1 This topic briefly introduces the logical

CITS 3242

Programming Paradigms

Part IV: Advanced Topics

Topic 21:

Lambda Calculus

1

This topic briefly introduces the logical foundation of functional

programming: the λ-calculus.

Page 2: CITS 3242 Programming Paradigms · 2011. 6. 12. · CITS 3242 Programming Paradigms Part IV: Advanced Topics Topic 21: Lambda Calculus 1 This topic briefly introduces the logical

Background The λ-calculus is the theoretical model underlying the

semantics and implementation of functional languages◦ It is a very simple language: it has nothing except functions.

Real functional languages are often described as “sugared λ-calculus”◦ The syntax is “sweetened” to make it easier for human

programmers.

◦ Also, some predefined constants and built-in functions are added for convenience, I/O, and because they can be implemented efficiently in hardware (e.g., numbers with +, *, ...).

Despite its simplicity, the λ-calculus is powerful enough to express all computable functions◦ No language can express more computations.

2

Page 3: CITS 3242 Programming Paradigms · 2011. 6. 12. · CITS 3242 Programming Paradigms Part IV: Advanced Topics Topic 21: Lambda Calculus 1 This topic briefly introduces the logical

Syntax λ -expressions come in three forms:

◦ x variables◦ M N applications◦ fun x -> E function abstractions

(where M,N and E must themselves be λ-expressions)

Parentheses group expressions, exactly as in F#◦ e.g. (fun x -> x) y

Capital letters (e.g. E, M, N) are used to denote λ-expressions.

◦ This is not part of the syntax – it’s used when talking about expressions.

Small letters denote variables (e.g. w, x, y, z, f, g, h)

◦ This is part of the syntax for λ-expressions.

Abstractions represent anonymous functions, just like in F#.

Aside: the traditional syntax for λ-calculus uses λ instead of fun.◦ e.g. (λx. x) y

3

Page 4: CITS 3242 Programming Paradigms · 2011. 6. 12. · CITS 3242 Programming Paradigms Part IV: Advanced Topics Topic 21: Lambda Calculus 1 This topic briefly introduces the logical

Semantics, abbreviations, bracketing

fun x -> E is a function that takes an argument called xand returns E (which may depend on x)

◦ e.g. fun x -> y x is a function that applies y to its argument

Function application is always prefix in the λ-calculus

◦ Infix functions are just a convenience in F#

◦ We’ll use +, *, 1, 2, ... as variables in some examples

◦ E.g. fun x -> + x 1

Nested abstractions are sometimes abbreviated, as in F#.

◦ e.g. fun x y z -> E means fun x -> fun y -> fun z -> E

Syntactically, abstractions extend as far as possible to the right subject to the presence of brackets (this is important!)

◦ e.g. (fun x -> x y) = fun x -> x y ≠ (fun x -> x) y

4

Page 5: CITS 3242 Programming Paradigms · 2011. 6. 12. · CITS 3242 Programming Paradigms Part IV: Advanced Topics Topic 21: Lambda Calculus 1 This topic briefly introduces the logical

Bound and free variablesThe x in the abstraction fun x -> E is called the bound variable of the abstraction

Consider the expressionfun x -> x (fun z -> x y z) (y z)

x is the bound variable of the outer abstraction

◦ the two xs refer to the same value

y is not bound by any abstraction

◦ y is a free variable

◦ the two ys refer to the same value

z is the bound variable of the inner abstraction

◦ the first z refers to this bound variable

◦ the second z is a free variable

◦ the two zs can refer to different values

Bound variables can be replaced with other variables without affecting the meaning, as long as the variable is not free in the body.

◦ E.g. fun x -> f x x = fun y -> f y y 5

Page 6: CITS 3242 Programming Paradigms · 2011. 6. 12. · CITS 3242 Programming Paradigms Part IV: Advanced Topics Topic 21: Lambda Calculus 1 This topic briefly introduces the logical

Replacement and Substitution

An fundamental operation in the study and implementation of functional languages is substitution

◦ substitution is the consistent replacement of a variable by an expression

The result of substituting the expression E for all free occurrences of the variable x in M is denoted by M[E/x], read “M with E for x”

M[E/x] is defined as follows:

x[E/x] = E

y[E/x] = y

(M N)[E/x] = (M[E/x]) (N[E/x])

(fun x -> M)[E/x] = fun x -> M (bound var’s aren’t replaced)

(fun y -> M)[E/x] = fun y -> (M[E/x]) (with y not free in E)

[We can always rename y so that y is not free in E.]

e.g. (g x (( fun x -> x y) (g x))) [f y/x] = g (f y) ((fun x -> x y) (g (f y)))

6

Page 7: CITS 3242 Programming Paradigms · 2011. 6. 12. · CITS 3242 Programming Paradigms Part IV: Advanced Topics Topic 21: Lambda Calculus 1 This topic briefly introduces the logical

Reduction Expressions are evaluated by reducing them using the following

rule whenever we have an application of a function abstraction:

(fun x ->E) M = E[M/x]

Aside: this is traditionally called b-conversion

An expression of the form (fun x ->E) M is called a redex(reducible expression)

Reductions can be done anywhere inside an expression that we have a redex, i.e an application of an abstraction.

A l-expression is in normal form if it contains no redexes

Evaluation of F# applications is similar, although we never evaluate within a function body

◦ Instead, the arguments are always fully evaluated, and then the application of the function to the arguments is reduced.

7

Page 8: CITS 3242 Programming Paradigms · 2011. 6. 12. · CITS 3242 Programming Paradigms Part IV: Advanced Topics Topic 21: Lambda Calculus 1 This topic briefly introduces the logical

Reduction orders Evaluation is basically the process of taking an expression and

reducing its redexes until there are none left

When there is more than one redex in an expression, we have to decide which one to reduce first

Normal-order reduction is defined as

“reduce the leftmost outermost redexes at each stage”

◦ Outermost means the redex is not inside another redex.

This differs from F#, but lazy languages like Haskell use a form of normal-order reduction.

The Church-Rosser Theorem says “if an expression can be reduced in two different ways to two normal forms, the normal forms must be equivalent up to renaming of bound variables.”

The Standardisation Theorem says “if an expression has a normal form, normal-order reduction is guaranteed to reach that normal form”

8

Page 9: CITS 3242 Programming Paradigms · 2011. 6. 12. · CITS 3242 Programming Paradigms Part IV: Advanced Topics Topic 21: Lambda Calculus 1 This topic briefly introduces the logical

Programming in the λ­calculus The λ­calculus is surprisingly powerful for a tiny

language with such a simple semantics (via reduction).

Historically, the λ­calculus was first studied by the logician Alonzo Church in 1932 as a simple language that could express every computable function.

It also is powerful enough that we can implement many of the constructs commonly found in programming languages: Booleans, pairs, numbers, lists, etc.

◦ Even recursion can be implemented!

The λ­calculus and extensions are often studied in language research and logic to avoid the need to consider complicated features that “could be implemented anyway” using just the λ­calculus.

9

Page 10: CITS 3242 Programming Paradigms · 2011. 6. 12. · CITS 3242 Programming Paradigms Part IV: Advanced Topics Topic 21: Lambda Calculus 1 This topic briefly introduces the logical

l-Expressions for let definitions l-expressions don’t include let definitions but

we can emulate them as follows.

Suppose we want to emulate the F# code:

let f x y = xlet g z = zf g g

Then we can emulate this with the following l-expression

(fun f g -> f g g) (fun x y -> x) (fun z -> z)

This allow us to structure our code similarly to F#10

Page 11: CITS 3242 Programming Paradigms · 2011. 6. 12. · CITS 3242 Programming Paradigms Part IV: Advanced Topics Topic 21: Lambda Calculus 1 This topic briefly introduces the logical

Booleans To implement Booleans, at a minimum we need expressions True, False and if

such that:

if True E M = E

if False E M = M

Everything is a function in the pure l-calculus, so True and False must be implemented using some kind of functions.

We can make True and False functions that take two arguments and select the first/second argument respectively. So we define:

True = fun x y -> x False = fun x y -> y

Then we define if as a higher-order function that just applies its first argument to the second and third.

if = fun b x y -> b x y

Then, we can use reduction to show that these definitions work correctly.

if True E M = (fun x y z -> x y z) (fun x y -> x) E M

= (fun y z. (fun x y -> x) y z) E M

= E

Evaluation is done by reduction – which shows that the expressions are equivalent, thus the implementation of Booleans works correctly.

11

Page 12: CITS 3242 Programming Paradigms · 2011. 6. 12. · CITS 3242 Programming Paradigms Part IV: Advanced Topics Topic 21: Lambda Calculus 1 This topic briefly introduces the logical

Lists Lists are built using two constructor functions.

Nil for the empty list ([])

Cons h t for the list with head h and tail t (h::t)

We can represent lists using l-expressions as follows:

For Nil we use the same selection function as True.

Nil = fun x y -> x

Cons h t selects y instead of x, and applies y to h and t.

Cons h t = fun x y -> y h t

Cons = fun h t x y -> y h t

This is like False, except that y is given the head and tail.

False = fun x y -> y

Then, we can use the following function to check whether a list is empty:

null = fun w -> w True (fun h t -> False)12

Page 13: CITS 3242 Programming Paradigms · 2011. 6. 12. · CITS 3242 Programming Paradigms Part IV: Advanced Topics Topic 21: Lambda Calculus 1 This topic briefly introduces the logical

List head & tail + Other data types

To take the head and tail of a list, or return a default d if the list is empty, we can use:

head = fun w d -> w d (fun h t -> h)

tail= fun w d -> w d (fun h t -> t)

Beyond lists and Booleans it’s possible to implement all kinds of data structures in the λ-calculus:

Pairs, triples, ... can be implemented as lists of size 2, 3, ...(Without types this is okay. There are better ways too.)

Numbers can be implemented as lists of Booleans, representing binary bit strings.

All discriminated union types can be implemented similarly to lists.

So, basically every F# type can be implemented! (except maybe imperative types like ref)

13

Page 14: CITS 3242 Programming Paradigms · 2011. 6. 12. · CITS 3242 Programming Paradigms Part IV: Advanced Topics Topic 21: Lambda Calculus 1 This topic briefly introduces the logical

Recursion

We can't write many interesting programs with lists or numbers

unless we have recursion. E.g. we can't write the equivalent of

let rec map f = function| [] -> []| x :: xs -> (f x) :: (map f xs)

How can we implement recursion?

It would need to be a function that “applies something to itself”.

More precisely, we want a function Y that satisfies:

Y f = f (Y f) = f (f (Y f) ) ... = f (f (f (f ...) ) )

It turns out that the following works as the function Y:

fun f -> (fun x -> f (x x)) (fun x -> f (x x))

14

Page 15: CITS 3242 Programming Paradigms · 2011. 6. 12. · CITS 3242 Programming Paradigms Part IV: Advanced Topics Topic 21: Lambda Calculus 1 This topic briefly introduces the logical

Why Y? Why do we need Y?

Suppose we try to translate the F# code for the map function into a

pure l-calculus expression.

let rec map f = function| [] -> []| x :: xs -> (f x) :: (map f xs)

We might try to translate this to the following expression for map.

fun f l -> l Nil (fun h t -> Cons (f h) (map f t))

But, this isn't a lambda-calculus expression unless we know what term

the map stands for!

Note: Nil and Cons are different to map in the above. They are just

abbreviations that we’re using to avoid writing the whole expression.

We know exactly expression they stand for.

But we don't know what to replace the map with. We'd like it to

recursively stand for the whole expression!

15

Page 16: CITS 3242 Programming Paradigms · 2011. 6. 12. · CITS 3242 Programming Paradigms Part IV: Advanced Topics Topic 21: Lambda Calculus 1 This topic briefly introduces the logical

Recursion using Y This is where we need to use Y. To do so, instead of map we use an

extra parameter m for the recursive calls

fun m f l -> l Nil (fun h t -> Cons (f h) (m f t))

Let’s call this expression MapStep.

Now, MapStep doesn't do what we want unless we supply the argument

m, which must be a function to do the map after the first step.

Applying Y to MapStep does exactly this:

Y MapStep = MapStep (Y MapStep)

... = MapStep (MapStep (MapStep (MapStep ....)))

So, this pure lambda-expression implements the map function.

16