Aarne Ranta (aarne@chalmers.se)

Book: 6.5, 7.3

Programming with states vs. values

Functions as values

Anonymous functions

Lambda calculus

Evaluation strategies

Simply typed lambda calculus

Polymorphic functions and principal types

Two **programming language paradigms**:

**Imperative** (aka. procedural): the execution of
a program is both evaluation of expressions and changing
a state (values of variables).

- "stateful" programming
- evaluation can have side effects

**Functional**: the execution of a program is just
evaluation of expressions, without changing the values
of variables.

- "stateless" programming
- no side effects

States and side effects have a double effect:

- enable efficient time and space consumption
- make it harder to reason about the program and therefore to optimize it

In the past, (1) dominated. Currently (2) is getting more important.

In fact, a powerful optimization technique is **single static assignments**,
where all variables are only given value once.

The levels are:

- modules
- definitions
- expressions

Thus no statements!

Example (could be written in a subset of Haskell):

doub x = x + x twice f x = f (f x) quadruple = twice doub main = print (twice quadruple 2)

So, is functional programming less expressive than imperative?

Sure we can write functions in C/C++/Java, too:

// doub x = x + x int doub (int x) { return x + x ; }

But this is what is called a **first-order function**: the arguments
are objects like numbers and class objects - but not themselves functions.

In C++, it is also possible to write **second-order functions**, which
take functions as arguments:

// twice f x = f (f x) int twice(int f (int n), int x) { return f(f(x)) ; }

In a functional language, a functions are **first-class citizens**.

This means: a function has a value even if it is not applied.

This is not quite so in C++: you cannot return a function as a value:

// quadruple = twice doub /* not possible (int f (int x)) quadruple(int x) { return twice(doub) ; } */ int quadruple(int x) { return twice(doub, x) ; }

The types of functions in Haskell are written in this way:

max : Int -> Int -> Int

The notation is right-associative, and hence equivalent to

max : Int -> (Int -> Int)

Thus, a "two-place function" is really a one-place function
whose value is also a function. (Haskell uses `::`

for typing,
but we stick to `:`

)

The typing rule for functions is:

Env => f : A -> B Env => a : A --------------------------------- Env => f a : B

Thus **partial application** is meaningful:

max 4 :: Int -> Int -- returns the greater one of its argument and 4

In many other languages, the value of a function must be a "basic type", i.e. not a function type.

This corresponds in Haskell to having a **tuple** of arguments:

maxt : (Int,Int) -> Int

The typing rule for tuples is

Env => a : A Env => b : B --------------------------- Env => (a,b) : (A,B)

Partial application is thus not meaningful: you have to form the tuple first.

The move

(A,B) -> C ==> A -> B -> C

is called **currying**, with reference to Haskell B. Curry.

At the same time as it is a powerful programming technique, it simplifies the semantics and implementation of programming languages.

In a functional language, you can form **anonymous functions**
by using **lambda abstraction**:

timesNine = twice (\x -> x + x + x)

In C++, you would have to write a named function for tripling:

// triple x = x + x + x int triple(int x) { return x + x + x ; } // timesNine = twice triple int timesNine(int x) { return twice(triple, x) ; }

There is a recent Lambda Library for C++ permitting this.

There is no difference in writing a function definition in these three ways:

foo x y = x + y * y foo x = \y -> x + y * y foo = \x -> \y -> x + y * y

We can thus simplify the abstract syntax of programs by saying that definitions have the form

Def ::= Ident "=" Exp

and treating the other form

Def ::= Ident [Ident] "=" Exp

as syntactic sugar:

id x_{1}... x_{n}= exp ===> id = \x_{1}-> ... \x_{n}-> exp

In this way, the environment is very simple: just mapping identifiers to expressions!

Now it is enough to work with the following tiny language:

Exp ::= Ident | Integer | "(" Exp Exp ")" | "(" "\" Ident "->" Exp ")"

Thus the expressions are variables, integer constants, applications, and
lambda abstractions. (Operations like `+`

can be treated as functions.)

For simplicity, we define the values to be expressions (notice that values are either integers or functions).

The big-step semantics is:

env => var ⇩ lookup env var env => const ⇩ const env => fun ⇩ (\x -> body) env => arg ⇩ val env => body(val/x) ⇩ result -------------------------------------------------------------------------------- env => (fun arg) ⇩ result env => (\x -> body) ⇩ (\x -> body)

Thus lambda abstractions are not evaluated when alone, but only when they are applied to arguments.

The operation `body(val/x)`

is **substitution**: replace all
occurrences of the variable `x`

by the expression `val`

in the expression `body`

.

Example:

(x + x + 3)(5/x) ⇩ 5 + 5 + 3

Definition, by syntax-directed translation

const(val/x) = const var(val/x) = val, if var == x var(val/x) = var, if var != x (fun arg)(val/x) = (fun(val/x) arg(val/x)) (\z -> body)(val/x) = (\z -> body(val/x)), if no capture

For abstraction, we need a condition to prevent **capture**.
Capture means that some `z`

in `val`

becomes bound by
the lambda.

Example evaluation:

twice = \f -> \a -> f (f a) double = \x -> x + x ((twice double) 3) = (((\f -> \a -> f (f a)) (\x -> x + x)) 3) = (((\a -> (\x -> x + x) ((\x -> x + x) a))) 3) = (((\a -> (\x -> x + x) (a + a))) 3) = (((\a -> a + a + a + a)) 3) = 3 + 3 + 3 + 3

Recall the unconditioned substitution rule:

(\z -> body)(val/x) = (\z -> body(val/x))

The following go wrong:

(\x -> x)(5/x) = (\x -> x(5/x)) -- should not replace a bound x = (\x -> 5) (\y -> x)(y/x) = (\y -> x(y/x)) -- should not bind a free y = (\y -> y)

Solution: **alpha conversion**:
change the name of a bound variable to a new one
that does not appear in the substitution.

(\x -> x)(5/x) = (\z -> z)(5/x) = (\z -> z(5/x)) = (\z -> z) (\y -> x)(y/x) = (\z -> x)(y/x) = (\z -> x(y/x)) = (\z -> y)

Two **evaluation strategies**.

**Call by value** (as above): evaluate argument before substitution

env => fun ⇩ (\x -> body) env => arg ⇩ val env => body(val/x) ⇩ result ---------------------------------------------------------------------------- env => (fun arg) ⇩ result

**Call by name** : substitute first, then evaluate

env => fun ⇩ (\x -> body) env => body(arg/x) ⇩ result -------------------------------------------------------- env => (fun arg) ⇩ result

What difference does it make?

- with call by value, all arguments are always evaluated
- with call by name, some arguments are never evaluated

C, C++, Java, ML use call by value.

Haskell uses a variant of call by name: call by need.

Consider the code

infinite = 1 + infinite first x y = x main = first 5 infinite

With call by value, we get

main = first 5 infinite = (\x -> \y -> x) 5 (1 + infinite) = (\y -> 5) (1 + infinite) = (\y -> 5) (2 + infinite) ...

With call by name,

main = first 5 infinite = (\x -> \y -> x) 5 infinite = (\y -> 5) infinite = 5

Generally: if an evaluation can terminate, it terminates with the call by name order. But not necessarily with call by value.

Call by name is sometimes called **lazy evaluation**: don't
evaluate until you need!

Disadvantage: you may need to evaluate the same expression twice:

doub x = x + x doub (doub 8) = doub 8 + doub 8 -- by name = 8 + 8 + 8 + 8 = 32 doub (doub 8) = doub 16 -- by value = 16 + 16 = 32

**Call by need**: change the environment by filling in the value
when an expression is evaluated. Next time, the value
is found from the environment and not computed again.

In **simply typed lambda calculus**, there is

- a set of basic types, such as
`int`

- function types of form
`typ1 -> typ2`

The typing rules are

env => var : A, if A == lookup env var env => const : int, for integer constants env => fun : A -> B env => arg : A ------------------------------------- env => (fun arg) : B env, x : A => body : B ---------------------------- env => (\x -> body) : A -> B

where `A, B`

are any types

What is the type of

\x -> x

There are infinitely many types:

int -> int (int -> int) -> (int -> int) (int -> int - int) -> (int -> int -> int)

All these types have the form

A -> A

Conversely, what ever type `A`

is, the expression has it.

We say that `\x -> x`

is **polymorphic**: it has many types.

Some polymorphism is possible in C++ using **templates**.

// twice : (A -> A) -> A -> A template<class A> A twice(A f (A n), A x) { return f(f(x)) ; }

Similarly in generic Java (Java 1.5):

// id : A -> A public static <A> A id(A x) { return x ; }

(and there are no functions of functions).

Templates in C++ and generics in Java were inspired by ML and Haskell.

In general, the result is polymorphic.

There is an algorithm returning **the most general type**. Thus

infer (\x -> x) = A -> A infer (\x -> \y -> x) = A -> B -> A infer (\f -> \x -> f (f x)) = (A -> A) -> A -> A infer (\x -> x + x) = int -> int

infer (\f -> \x -> f (f x)) : t t = a -> b -> c ==> f (f x) : c f : d -> e ==> a = d -> e because f : a c = e because f _ : e f x : d because f : d -> _ f x : e because f : _ -> e hence d = e x : d ==> d = b because x : b we have a = d -> e = b -> b c = d = b hence t = (b -> b) -> b -> b

Isn't this mind-twisting - a little bit like Sudoku? Can it be made systematic?

The algorithm calls **unification**: Book 6.5.4

Unification finds solutions to equations with type variables.

Unification algorithm: Book 6.5.5

env => var : A', if A == lookup env var and A > A' env => const : int, for integer constants env => fun : A -> B env => arg : A ------------------------------------- env => (fun arg) : B env, x : A => body : B ---------------------------- env => (\x -> body) : A -> B

Here `A > A'`

means that `A'`

is an **instance** of `A`

. Examples:

a > Int a > Int -> Int a > b -> c a -> b > Int -> (Int -> b)

where `a, b, c`

are type variables.

Input: two types with variables.

Output: **type substitution** *s* (mapping from variables to types).

Notation: *Ts* is type *T* after performing substitution *s*.

Property: if *s* = unify *T U*, then *Ts* = *Us*

unify a b = {a = b} -- a,b distinct variables unify a T = {a = T} -- T non-variable, a doesn't occur in T unify (T -> U) (T' -> U') = s1 := unify T T' s2 := unify Us1 U's1 return s2 + s1

**Occurs check**: we cannot unify e.g. `a`

with `a -> b`

.

Input: environment mapping variables to their types, expression.

Output: substitution, type.

infer env x = ({}, fresh env (lookup x env)) infer env n = ({}, Int) infer env (fun arg) = (s1,T1) := infer env fun (s2,T2) := infer (env s1) arg b := freshVar env s3 := unify (T1 s2) (T2 -> b) return (s3 + s2 + s1, b s3) infer env (\x -> body) = b := freshVar env (s,T) := infer (env, x : b) body return (s, (b s) -> T)

The auxiliary `freshVar`

returns a type variable not yet occurring in env.

infer () (\f -> \x -> f (f x)) infer (f : a, x : b) (f (f x)) ({},c) <- infer (f:a, x:b) f ({},d) <- infer (f:a, x:b) x {c = d -> e} <- unify c (d -> e) ({c = d -> e}, e) <- infer env (f x) {c = e -> e} <- unify c (e -> e) ({c = d -> e, c = e -> e, d = e}, e) <- infer env (f (f x)) (subst, e -> e) <- infer env (\x -> f (f x)) (subst, (e -> e) -> e -> e) <- infer env (\f -> \x -> f (f x))

with some shortcuts and "obvious" abbreviations env and subst... it's better to implement and try out!