Lecture 10: Functional Programming Languages
Programming Languages Course
Aarne Ranta (aarne@chalmers.se)
%!target:html
%!Encoding:utf-8
%!postproc(html): #NEW
%!postproc(html): #HR
%!postproc(html): #sub1 1
%!postproc(html): #subn1 n-1
%!postproc(html): #subn n
Book: 6.5, 7.3
#NEW
==Plan==
Programming with states vs. values
Functions as values
Anonymous functions
Lambda calculus
Evaluation strategies
Simply typed lambda calculus
Polymorphic functions and principal types
#NEW
==Imperative vs. functional programming==
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.
#NEW
==The structure of a functional program==
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)
```
#NEW
==Functions in imperative languages==
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)) ;
}
```
#NEW
==Functions as first-class citizens==
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) ;
}
```
#NEW
==Function types and partial application==
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
```
#NEW
==Functions of tuples, currying==
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.
#NEW
==Anonymous functions==
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 http://www.boost.org/doc/libs/1_38_0/doc/html/lambda.html]
for C++ permitting this.
#NEW
==Desugaring function definitions==
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#sub1 ... x#subn = exp ===> id = \x#sub1 -> ... \x#subn -> exp
```
In this way, the environment is very simple: just mapping
identifiers to expressions!
#NEW
==Evaluating 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``.
#NEW
==Substitution==
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
```
#NEW
==Examples of capture==
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)
```
#NEW
==Call by value vs. call by name==
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.
#NEW
==Termination of evaluation==
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.
#NEW
==Lazy evaluation and call by need==
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.
#NEW
==Types for lambda calculus==
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
#NEW
==Polymorphism==
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.
#NEW
==Polymorphism in C++ and Java==
Some polymorphism is possible in C++ using **templates**.
```
// twice : (A -> A) -> A -> A
template 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 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.
#NEW
==Type inference in functional languages==
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
```
#NEW
==An example of type inference==
```
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?
#NEW
==Implementing type inference==
The algorithm calls **unification**: Book 6.5.4
Unification finds solutions to equations with type variables.
Unification algorithm: Book 6.5.5
#NEW
==Polymorphic typing rules==
```
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.
#NEW
==The unification algorithm==
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``.
#NEW
==Type inference with unification==
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.
#NEW
==Type inference example revisited==
```
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!