# lecture05

```Programs as data
Higher-order functions,
polymorphic types,
and type inference
Peter Sestoft
Monday 2013-09-16
www.itu.dk
1
Plan for today
•  Higher-order functions in F#
•  A higher-order functional language
•  F# mutable references
•  Polymorphic types
–  Informal procedure
–  Type rules
–  Unification
–  The union-find data structure
–  Type inference algorithm
•  Variant generic types in Java and C#
–  Java use-side variance
–  C# 4.0 declaration-side variance
www.itu.dk
2
Higher-order functions
and anonymous functions in F#
•  A higher-order function takes another
function as argument
(’a->’b) -> (’a list -> ’b list)
let rec map f xs =
match xs with
| []
-> []
| x::xr -> f x :: map f xr
let mul2 x = 2.0 * x;;
map mul2 [4.0; 5.0; 89.0];;
[8.0; 10.0; 178.0]
•  Anonymous functions
map (fun x -> 2.0 * x) [4.0; 5.0; 89.0]
[false; false; true]
map (fun x -> x > 10.0) [4.0; 5.0; 89.0]
www.itu.dk
3
Function types in C#
unit -> R
•  Delegate types
A1 -> R
delegate R Func<R>()
delegate R Func<A1,R>(A1 x1)
delegate R Func<A1,A2,R>(A1 x1, A2 x2)
A1 * A2 -> R
delegate void Action<A1>(A1 x1)
delegate void Action<A1,A2>(A1 x1, A2 x2)
•  Anonymous method expressions
delegate(int x) { return x>10; }
delegate(int x) { return x*x; }
(int x) => x>10
x => x>10
x => x*x
C# 3.0
A1 -> unit
A1*A2 -> unit
Func<int,bool>
Func<int,int>
fun (x:int) -> x>10
fun x -> x>10
fun x -> x*x
F#
4
Uniform iteration over a list
let rec sum xs =
match xs with
| []
-> 0
| x::xr -> x + sum xr
int list -> int
let rec prod xs =
match xs with
| []
-> 1
| x::xr -> x * prod xr
•  Generalizing 0/1 to e, and +/* to f:
let rec foldr f xs e =
match xs with
| []
-> e
| x::xr -> f x (foldr f xr e)
('a -> 'b -> 'b) ->
'a list -> 'b -> 'b
List.foldBack in F#
The foldr function replaces :: by f, and [] by e:
foldr ◊ (x1::x2::…::xn::[]) e = x1 ◊ (x2 ◊ (... ◊ (xn ◊ e) …))
F# mutable references
•  A reference is a cell that can be updated
Create int reference
let r = ref 177
!r
Dereference
(r := !r+1; !r)
!r
Assign to reference
•  Useful for generation of new names etc:
let nextlab = ref -1;;
let newLabel () = (nextlab := 1 + !nextlab;
"L" + string (!nextlab));;
newLabel();;
newLabel();;
newLabel();;
www.itu.dk
Higher-order micro-ML/micro-F#
•  Higher-order functional language
–  A function may be given as argument:
let twice g x = g(g x)
–  A function may be returned as result
let add x = let f y = x+y in f
let x = 77
arguments!
•  Closures are needed:
–  The function returned must enclose the value of
f’s parameter x – has nothing to do with later x
•  Same micro-ML syntax: Fun/Absyn.fs
www.itu.dk
7
Interpretation of a higher-order language
•  The closure machinery is already in place
•  Just redefine function application:
let rec eval (e : expr) (env : value env) : value =
match e with
| ...
| Call(eFun, eArg) ->
let fClosure = eval eFun env
in match fClosure with
| Closure (f, x, fBody, fDeclEnv) ->
let xVal = eval eArg env
let fBodyEnv =
(x, xVal) :: (f, fClosure) :: fDeclEnv
in eval fBody fBodyEnv
| _ -> failwith "eval Call: not a function"
www.itu.dk
8
ML/F#-style
parametric polymorphism
let f x = 1
in f 2 + f true
int -> int
Type for f is
’a -> int
bool -> int
•  Each expression has a compile-time type
•  The type may be polymorphic (‘many forms’)
and have multiple type instances
www.itu.dk
9
Type generalization and specialization
•  If f has type (α → int) and α appears
nowhere else, the type gets generalized to a
type scheme written ∀α.(α → int):
let f x = 1
∀α.(α → int)
•  If f has type scheme ∀α.(α → int) then α may
be instantiated by/specialized to any type:
f
f
f
f
42
false
[22]
(3,4)
f : int → int
f : bool → int
f : int list → int
f : int*int → int
www.itu.dk
10
Polymorphic type inference
•  F# and ML have polymorphic type inference
•  Static types, but not explicit types on functions
α = β→δ
α β
let twice g y = g (g y)
(β→β) → (β→β)
β=δ
β=δ=ε
and δ=ε
so
so
αα==β→β
β→β
α = δ→ε
•  We generalize β, so twice gets the type scheme
∀β. (β→β) → (β→β), hence “β may be any type”
let mul2 y = 2 * y
twice mul2 11
mul: int -> int
twice : (int->int)->(int->int)
Basic elements of type inference
•  “Guess” types using type variables α, β, …
•  Build and solve “type equations” α = β→δ …
•  Generalize types of let-bound variables/funs.
to obtain type schemes ∀β. (β→β) → (β→β)
•  Specialize type schemes at variable use
•  This type system has several names:
–  ML-polymorphism
–  let-polymorphism
–  Hindley-Milner polymorphism (Hindley 1969 &
Milner 1978)
www.itu.dk
12
Restrictions on ML polymorphism, 1
•  Only let-bound variables and functions can
have a polymorphic type
•  A parameter’s type is never polymorphic:
let f g = g 7 + g false
Ill-typed:
parameter g never
polymorphic
•  A function is not polymorphic in its own body:
let rec h x =
if true then 22
else h 7 + h false
Ill-typed: h not
polymorphic in its
own body
www.itu.dk
13
Restrictions on ML polymorphism, 2
•  Types must be finite and non-circular
f not polymorphic
in its own body
let rec f x = f f
•  Guess x has type α
•  Then f must have type α→β for some β
•  But because we apply f to itself in (f f), we
must have α = α→β
•  But then α = (α→β)→β = ((α→β) →β)→β = …
is not a finite type
•  So the example is ill-typed
www.itu.dk
14
Restrictions on ML polymorphism, 3
•  A type parameter that is used in an enclosing
scope cannot be generalized
g : β→int
α
α= β
let f x = β
let g y = if x=y then 11 else 22
in g false
in f 42
α bound in outer
scope, cannot
Ill-typed: function g
generalize β
not polymorphic
•  Reason: If this were well-typed, we would
compare x (42) with y (false), not good…
www.itu.dk
15
Joint exercises
•  Which of these are well-typed, and why/not?
let f x = 1
in f f
let f g = g g
let f x =
let g y = y
in g false
in f 42
let f x =
let g y = if true then y else x
in g false
in f 42
Type rules for ML-polymorphism
Specialize from
typescheme
Generalize to
typescheme
Joint exercises
•  Draw the type trees for some of these
let x = 1
in x < 2
let f x = 1
in f 2 + f false
let f x = 1
in f f
www.itu.dk
18
Programming type inference
•  Algorithm W (Damas & Milner 1982) with
many later improvements
•  Symbolic type equation solving by
–  Unification
–  The union-find data structure
•  “Not free in ρ” formalized by binding levels:
0
α:0
α= β
let f x =
:1
β:0
1 let g y = if x=y then 11 else 22
in g false
in f 42
•  Since β-level < g-level, do not generalize β
www.itu.dk
Unification of two types, unify(t1,t2)
Type t1
Type t2
Action
int
int
No action
bool
bool
No action
t1x → t1r
t2x → t2r
unify(t1x,t2x) and unify(t1r,t2r)
α
α
No action
α
β
Make α=β
α
t2
Make α=t2 unless t2 contains α
t1
β
Make β=t1 unless t1 contains β
All other cases
Failure, type error!
www.itu.dk
The union-find data structure
•  A graph of nodes (type variables) divided
into disjoint classes
•  Each class has a representative node
•  Operations:
–  New: create new node (type variable)
–  Find(n): find representative of node n’s class
–  Union(n1,n2): join the classes of n1 and n2
www.itu.dk
21
Type inference for micro-ML, 1
let rec typ (lvl : int) (env : tenv) (e : expr) : typ =
match e with
| CstI i -> TypI
| CstB b -> TypB
| Var x -> specialize lvl (lookup env x)
| ...
typ ρ e = t
if and only if
www.itu.dk
22
Type inference for micro-ML, 2
let rec typ (lvl : int) (env : tenv) (e : expr) : typ =
match e with
| Prim(ope, e1, e2) ->
let t1 = typ lvl env e1
let t2 = typ lvl env e2
match ope with
| "*" -> (unify TypI t1; unify TypI t2; TypI)
| "+" -> (unify TypI t1; unify TypI t2; TypI)
| "=" -> (unify t1 t2; TypB)
| "<" -> (unify TypI t1; unify TypI t2; TypB)
| "&" -> (unify TypB t1; unify TypB t2; TypB)
| _
-> failwith ("unknown primitive " ^ ope)
Type inference for micro-ML, 3
let rec typ (lvl : int) (env : tenv) (e : expr) : typ =
match e with
| If(e1, e2, e3) ->
let t2 = typ lvl env e2
let t3 = typ lvl env e3
unify TypB (typ lvl env e1);
unify t2 t3;
t2
www.itu.dk
24
Type inference for micro-ML, 4
let rec typ (lvl : int) (env : tenv) (e : expr) : typ =
match e with
| ...
| Let(x, eRhs, letBody) ->
let lvl1 = lvl + 1
let resTy = typ lvl1 env eRhs
let letEnv = (x, generalize lvl resTy) :: env
typ lvl letEnv letBody
| ...
www.itu.dk
25
Properties of ML-style polymorphism
•  The type found by the inference algorithm is
the most general one: the principal type
•  Consequence: Type checking can be modular
•  Types can be large and type inference slow:
let
let
let
let
let
let
let
id x
pair
p1 p
p2 p
p3 p
p4 p
p5 p
=
x
=
=
=
=
=
x
y p = p
pair id
pair p1
pair p2
pair p3
pair p4
x y
id p
p1 p
p2 p
p3 p;;
p4 p;;
Exponentially
many type
variables!
•  In practice types are small and inference fast
www.itu.dk
Type inference in C# 3.0
var x = “hello”;
… x.Length …
x = 17;
// Inferred type: String
// Type error
•  No polymorphic generalization
•  Can infer parameter type of anonymous
function from context: xs.Where(x=>x*x>5)
•  Cannot infer type of anonymous function
•  Parameter types in methods
–  must be declared
–  cannot be inferred, because C# allows method
www.itu.dk
27
Polymorphism (generics) in Java and C#
•  Polymorphic types
interface IEnumerable<T> { ... }
class List<T> : IEnumerable<T> { ... }
struct Pair<T,U> { T fst; U snd; ... }
delegate R Func<A,R>(A x);
•  Polymorphic methods
void Process<T>(Action<T> act, T[] xs)
void <T> Process(Action<T> act, T[] arr)
C#
Java
•  Type parameter constraints
void Sort<T>(T[] arr) where T : IComparable<T>
void <T extends Comparable<T>> Sort(T[] arr)
www.itu.dk
C#
Java
28
Variance in type parameters
•  Assume Student subtype of Person
void PrintPeople(IEnumerable<Person> ps) { ... }
IEnumerable<Student> students = ...; Java and C# 3 say
PrintPeople(students);
NO: Ill-typed!
•  C# 3 and Java:
–  A generic type is invariant in its parameter
–  I<Student> is not subtype of I<Person>
•  Co-variance (co=with):
–  I<Student> is subtype of I<Person>
•  Contra-variance (contra=against):
–  I<Person> is subtype of I<Student>
www.itu.dk
29
Co-/contra-variance is unsafe in general
•  Co-variance is unsafe in general
List<Student> ss = new List<Student>(); Wrong!
List<Person> ps = ss;
Because would allow
writing Person to
Student s0 = ss[0];
Student list
•  Contra-variance is unsafe in general
List<Person> ps = ...;
List<Student> ss = ps;
Student s0 = ss[0];
•  But:
Wrong!
Because would allow
Person list
–  co-variance OK if we only read (output) from list
–  contra-variance OK if we only write (input) to list
www.itu.dk
30
Java 5 wildcards
•  Use-side co-variance
void PrintPeople(ArrayList<? extends Person> ps) {
for (Person p : ps) { … }
}
...
OK!
PrintPeople(new ArrayList<Student>());
•  Use-side contra-variance
void AddStudentToList(ArrayList<? super Student> ss) {
}
...
OK!
www.itu.dk
31
Co-variance in interfaces (C# 4)
•  When an I<T> only produces/outputs T’s,
it is safe to use an I<Student>
where a I<Person> is expected
•  This is co-variance
•  Co-variance is declared with the out modifier
interface IEnumerable<out T> {
IEnumerator<T> GetEnumerator();
}
interface IEnumerator<out T> {
T Current { get; }
}
•  Type T can be used only in output position;
e.g. not as method argument (input)
www.itu.dk
32
Contra-variance in interfaces (C# 4)
•  When an I<T> only consumes/inputs T’s,
it is safe to use an I<Person>
where an I<Student> is expected
•  This is contra-variance
•  Contra-variance is declared with in modifier
interface IComparer<in T> {
int Compare(T x, T y);
}
•  Type T can be used only in input position;
e.g. not as method return type (output)
www.itu.dk
33
Variance in function types (C# 4)
•  A C# delegate type is
–  co-variant in return type (output)
–  contra-variant in parameters types (input)
•  Return type co-variance:
Func<int,Student> nthStudent = ...
Func<int,Person> nthPerson = nthStudent;
•  Argument type contra-variance:
Func<Person,int> personAge = ...
Func<Student,int> studentAge = personAge;
•  F# does not support co-variance or contravariance (yet?)
www.itu.dk
34
•  This week’s lecture:
–  PLC sections A.11-A.12 and 5.1-5.5 and 6.1-6.7
–  Exercises 6.1, 6.2, 6.3, 6.4, 6.5
•  No lecture next week
•  Next lecture, Monday 30 September:
–  PLCSD chapter 7
–  Strachey: Fundamental Concepts in …
–  Kernighan & Richie: The C programming
language, chapter 5.1-5.5
www.itu.dk
35
```