Understanding Type Inference in Simply Typed Lambda Calculus - Prof. Jeffrey S. Foster, Study notes of Computer Science

An introduction to type inference in the context of simply typed lambda calculus. It covers the basics of type environments, type judgments, and type rules. The document also includes examples of type checking and the algorithm for type checking. Additionally, it discusses product types, sum types, recursive types, and folding and unfolding. The document concludes by discussing the benefits and drawbacks of type inference.

Typology: Study notes

Pre 2010

Uploaded on 02/13/2009

koofers-user-ud2-1
koofers-user-ud2-1 🇺🇸

10 documents

1 / 11

Toggle sidebar

This page cannot be seen from the preview

Don't miss anything!

bg1
Type Systems
CMSC 631 – Program Analysis and
Understanding
Fall 2003
2
CMSC 631, Fall 2003
Consider the (untyped) lambda calculus
false = λx.λy.x
0 (Scott) = λx.λy.x
Everything is encoded as a function
So we can easily misuse combinators
-false 0 if 0 then ... etc...
This is no better than assembly language!
The Need for a Type System
3
CMSC 631, Fall 2003
A type system is some mechanism for distinguishing
good programs from bad
Good programs = well typed
Bad programs = ill typed or not typable
Examples:
0 + 1 // well typed
false 0 // ill-typed: can’t apply a boolean
1 + (if true then 0 else false) // ill-typed: can’t add
boolean to integer
What is a Type System?
4
CMSC 631, Fall 2003
“A type system is a tractable syntactic method for
proving the absence of certain program behaviors
by classifying phrases according to the kinds of
values they compute.
– Benjamin Pierce, Types and Programming Languages
A Definition of Type Systems
5
CMSC 631, Fall 2003
e ::= n | x | λx:t.e | e e
Functions include the type of their argument
We don’t really need this, but it will come in handy
t ::= int | t t
t1 t2 is a the type of a function that, given an
argument of type t1, returns a result of type t2
-t1 is the domain, and t2 is the range
Simply-Typed Lambda Calculus
6
CMSC 631, Fall 2003
Our type system will prove judgments of the form
A e : t
“In type environment A, expression e has type t
Type Judgments
pf3
pf4
pf5
pf8
pf9
pfa

Partial preview of the text

Download Understanding Type Inference in Simply Typed Lambda Calculus - Prof. Jeffrey S. Foster and more Study notes Computer Science in PDF only on Docsity!

Type Systems CMSC 631 – Program Analysis and Understanding Fall 2003 CMSC 631, Fall 2003 2

• Consider the (untyped) lambda calculus

■ (^) false = λx.λy.x ■ (^) 0 (Scott) = λx.λy.x

• Everything is encoded as a function

■ (^) So we can easily misuse combinators

  • false 0 if 0 then ... etc... ■ (^) This is no better than assembly language!

The Need for a Type System

CMSC 631, Fall 2003 3

• A type system is some mechanism for distinguishing

good programs from bad

■ Good programs = well typed ■ Bad programs = ill typed or not typable

• Examples:

■ (^) 0 + 1 // well typed ■ (^) false 0 // ill-typed: can’t apply a boolean ■ 1 + (if true then 0 else false) // ill-typed: can’t add boolean to integer

What is a Type System?

CMSC 631, Fall 2003 4

“A type system is a tractable syntactic method for

proving the absence of certain program behaviors

by classifying phrases according to the kinds of

values they compute.”

  • Benjamin Pierce, Types and Programming Languages

A Definition of Type Systems

• e ::= n | x | λx:t.e | e e

■ Functions include the type of their argument ■ We don’t really need this, but it will come in handy

• t ::= int | t → t

■ t1 → t2 is a the type of a function that, given an argument of type t1, returns a result of type t

  • t1 is the domain , and t2 is the range

Simply-Typed Lambda Calculus

• Our type system will prove judgments of the form

■ (^) A ๵ e : t ■ (^) “In type environment A, expression e has type t”

Type Judgments

CMSC 631, Fall 2003 7

• A type environment is a map from variables to

types (a kind of symbol table)

■ ෘ is the empty type environment

  • A closed term e is well-typed if ෘ ๵ e : t for some t
  • We’ll abbreviate this as ๵ e : t ■ (^) A, x:t is just like A, except x now has type t
  • The type of x in A, x:t is t
  • The type of^ z≠x^ in^ A,^ x:t^ in the type of^ z^ in^ A

• When we see a variable in a program, we look in

the type environment to find its type

Type Environments

CMSC 631, Fall 2003 8

Type Rules

A ๵ n : int x dom(A) A ๵ x : A(x) A, x:t ๵ e : t′ A ๵ λx:t.e : t→t′ A ๵ e1 : t→t′ A ๵ e2 : t A ๵ e1 e2 : t CMSC 631, Fall 2003 9

Example

  • dom(A) A ๵ - : int→int^ A^ ๵^ 3 :^ int A ๵ - 3 : int A = - : int→int CMSC 631, Fall 2003 10

Another Example

  • dom(B) x dom(B) B ๵ 3 : int A ๵ 4 : int B ๵ + : i→i→i B ๵x : i B ๵ + x : int→int B ๵ + x 3 : int A ๵ (λx:int.+ x 3) : int→int A ๵ (λx:int.+ x 3) 4 : int A = + : int→int→int B = A, x : int We’d usually use infix x + 3

• Our type rules are deterministic

■ (^) For each syntactic form, only one possible rule ■

• They define a natural type checking algorithm

■ (^) TypeCheck : type env × expression → type TypeCheck(A, n) = int TypeCheck(A, x) = if x in dom(A) then A(x) else fail TypeCheck(A, λx:t.e) = TypeCheck((A, x:t), e) TypeCheck(A, e1 e2) = let t1 = TypeCheck(A, e1) in let t2 = TypeCheck(A, e2) in if dom(t1) = t2 then range(t1) else fail

An Algorithm for Type Checking

• Here is our semantics, with integers

■ (^) Notice that the last rule requires that e1 is a function

  • The other cases are^ undefined (i.e.,^ an error)

Semantics

(λx.e1) → l^ (λx.e1) e1 → l^ λx.e e[e2\x] → l^ e′ e1 e2 → l^ e′ n → l^ n

CMSC 631, Fall 2003 19

• Self application is not checkable in our system

■ (^) It would require a type t such that t = t→t′

  • (We’ll see this next, but so far...)

• The simpl^ - y-typed lambda calculus is strongly

normalizing

■ Every program has a normal form ■ I.e., every program halts!

Self Application and Types

A, x:? ๵ x : t t A, x:? ๵ x : t A, x:? ๵ x x : ... A ๵ λx:?.x x : ... CMSC 631, Fall 2003 20

• We can type self application if we have a type to

represent the solution to equations like t = t→t′

■ (^) We define the type μα.t to be the solution to the (recursive) equation α = t ■ (^) Example: μα.int→α

Recursive Types

→ int int int int → → → or → int CMSC 631, Fall 2003 21

• We can check type equivalence with the

previous definition

■ Standard unification, omit occurs checks ■

• Alternative solution:

■ (^) The programmer puts in explicit fold and unfold operations to expand/contract one “level” of the type trees

  • unfold^ μα.t = t[μα.t\α]
  • fold t[μα.t\α] = μα.t

Folding and Unfolding

→ int → int int → unfold fold CMSC 631, Fall 2003 22

• In the pure lambda calculus, every term is typable

with recursive types

■ (Pure = variables, functions, applications only)

• Most languages have some kind of “recursive” type

■ E.g., for data structures like lists, tree, etc. ■

• However, usually two recursive types that define

the same structure but use a different name are

considered different

■ (^) E.g., struct foo { int x; struct foo *next; } is different from struct bar { int x; struct bar *next; }

Discussion

e ::= x | λx.e | e e

| ref e allocation

| !e dereference

| e := e assignment

| e; e sequencing

• Notice that this is not C

  • Variables cannot be updated; only references can
  • I.e., there are no l-values or r-values

• This is a language with updatable references

An Imperative Language

!(ref 0)

let x = ref 0 in

x := !x + 1

let x = ref 0 in

λy. x := !x + 1; !x

Examples

CMSC 631, Fall 2003 25

• t ::= ... | ref t

■ Note: in ML this type is written t ref

Type Checking Rules

A ๵ e : t A ๵ ref e : ref t A ๵ e : ref t A ๵ !e : t A ๵ e1 : ref t A ๵ e2 : t A ๵ e1 := e2 : t CMSC 631, Fall 2003 26

• Sometimes in imperative programs we write

expressions that have some side effect but no

interesting result

• To represent this directly, use unit:

■ e ::= ... | () ■ t ::= ... | unit

Unit and the Unit Type

A ๵ e1 : ref t A ๵ e2 : t A ๵ () : unit A ๵ e1 := e2 : unit CMSC 631, Fall 2003 27

• Now we need to keep track of memory

■ (^) State is a map from locations to values ■ (^) Our redexes will be tuples ‹State, expression› ■ (^) As a consequence, order of evaluation matters

• As before, evaluation will yield a fully-evaluated

term, also called a value

■ v ::= x | λx.e ■ (^) e ::= v | e e | ref e | !e | e := e

Operational Semantics

CMSC 631, Fall 2003 28

Operational Semantics (cont’d)

‹S, e› → ‹S′, v› loc fresh ‹S, ref e› → ‹S[v\loc], loc› ‹S, (λx.e1)› → ‹S, (λx.e1)› ‹S, e1› → S , v1› ‹S , e2› → S , v2› ‹S, e1; e2› → S , v2›

Operational Semantics (cont’d)

‹S, e1› → S , λx.e› S , e2› → S , v› S ,e[v\x]› → S , v′› ‹S, e1 e2› → S , v′› ‹S, e› → ‹S′, loc› ‹S, !e› → ‹S′, S′(loc)› ‹S, e1› → ‹S′, loc› ‹S′, e2› → ‹S′′, v› ‹S, e1 := e2› → ‹S′′[v\loc], v›

• We’ve discussed simple types so far

■ (^) Integers, functions, pairs, unions ■ (^) Extensions for recursive types and updatable refs ■

• Type systems have nice properties

■ Type checking is straightforward (needs annotations) ■ Well typed programs don’t go “wrong”

  • They^ don’t^ get stuck in the operational semantics

• But...We can’t type check all good programs

Recap

CMSC 631, Fall 2003 37

• We don’t have recursive types, so we shouldn’t

infer them

• So in the operation C[t\α], require that α FV(t)

• In practice, it may better to allow α FV(t) and do

the occurs check at the end

■ (^) But that can be awkward to implement

Occurs Check

CMSC 631, Fall 2003 38

• Computing C[t\α] by substitution is inefficient

• Instead, use a union-find data structure to

represent equal types

■ (^) The terms are in a union-find forest ■ (^) When a variable and a term are equated, we union them so they have the same ECR ■ Note: Only need to maintain ECR of variables, not of all terms, though doing terms as well has some potential advantages

Unifying a Variable and a Type

CMSC 631, Fall 2003 39

Example

α γ α=int→β γ =int→int α= γ β

int int

int CMSC 631, Fall 2003 40

• The process of finding a solution to a set of

equality constraints is called unification

■ Original algorithm due to Robinson

  • But his algorithm was inefficient ■ (^) Often written out in different form
  • See Algorithm W ■ (^) Constraints usually solved on-line
  • As type inference rules applied

Unification

• The algorithm we’ve given finds the most general

type of a term

■ Any other valid type is “more specific,” e.g.,

  • λx.x : int → int ■ Formally, any other valid type can be gotten from the most general type by applying a substitution to the type variables

• This is still a monomorphic type system

■ (^) α stands for “some particular type, but it doesn’t matter exactly which type it is”

Discussion

• Observation: λx.x returns its argument exactly

and does not place any constraints on the type

of x

■ (^) The identity function works for any argument type

• We can express this with universal quantification:

■ (^) λx.x : α α→α ■ (^) For any type α, the identity function has type α→α ■ (^) This is also known as parametric polymorphism

Parametric Polymorphism

CMSC 631, Fall 2003 43

• When we use a parametric polymorphic type, we

instantiate it with a particular type

■ For now, the programmer specifies this by hand ■ (^) (λx.x)[S] : S → S ■ (λx.x)[T] : T → T

• This is where the term parametric comes from

■ (^) The type α α→α is a “function” in the domain of types, and it is passed a parameter at instantiation time ■ Sometimes this type is written α α→α

Instantiation

CMSC 631, Fall 2003 44

• We’re going to need to perform substitutions on

quantified types

■ So just like with lambda calculus, we need to worry about free variables and capture-free substitution

• Define the free variables of a type

■ FV(c) = ■ (^) FV(t→t′) = FV(t) FV(t′) ■ (^) FV( α.t) = FV(t) - {α}

  • Look familiar?

Free Variables, Again

CMSC 631, Fall 2003 45

• Define t[u\α] as

■ (^) α[u\α] = u ■ (^) β[u\α] = β where β≠α ■ (^) (t→t′)[u\α] = t[u\α]→t′[u\α] ■ ( β.t)[u\α] = β.(t[u\α]) where β≠α and β FV(u) ■

• Look familiar?

Substitution, Again

CMSC 631, Fall 2003 46

• Notice we may need to use alpha conversion on

the quantified type to avoid capture

• Notice we can substitute in any type

■ (^) That’s what the for all means!

Instantiation Rule

A ๵ e : α.t A ๵ e[t ] : t[t \α]

• Question: When is it safe to generalize

(quantify) a type variable α in the type of

expression e?

• Answer: Whenever we can redo the typing

proof for e, choosing α to be anything we want,

and still have a valid typing proof.

Generalization

• The choice of the type of x is purely local to

type checking λx.x

■ There is no interaction with the outside environment ■ Thus we can generalize the type of x

Examples

A, x:α ๵ e : α A ๵ λx.x : α→α A, x:int ๵ x : int A ๵ λx.x : int→int A, x:(i→i) ๵ x : (i→i) A ๵ λx.x : (i→i)→(i→i)

CMSC 631, Fall 2003 55

• Restrict polymorphism to only the “top level”

• Only introduce polymorphism at let

• Always fully instantiate when we use a variable

with a polymorphic type

■ (^) e ::= n | x | λx.e | e e | let x = e in e ■ (^) s ::= t | α.s

  • These are type schemes ■ (^) t ::= α | int | t → t

Hindley-Milner Polymorphism

CMSC 631, Fall 2003 56

Old Type Inference Rules

A ๵ n : int A, x:α ๵ e : t′ α fresh A ๵ λx.e : α→t′ A ๵ e1 : t 1 A ๵ e2 : t t1 = t2 →β β fresh A ๵ e1 e2 : β CMSC 631, Fall 2003 57

• At let, generalize over all possible variables

• At variable uses, instantiate to all fresh types

New Type Inference Rules

A(x) = α.t β fresh A ๵ x : t[β\α] A ๵ e1 : t1 A,x: α.t ๵ e2 : t2 α A ๵ let x = e1 in e2 : t → → → → → (^) → CMSC 631, Fall 2003 58

• Parametric polymorphic type inference

let x = λx.x in // x : α.α→α x 3; // x : β→β, β=int x (λy.y) // x : γ→γ, γ=δ→δ

• This would be untypable in a monomorphic type

system

Example

• A type inference algorithm that explicitly solves

the equality constraints on-line

• Instead of implicit global substitution (like we

used before), threads the substitution through

the inference

• In practice, use previously algorithm, plus

generalize at let and instantiate at variable uses

Algorithm W

• Suppose we want polymorphism in our

imperative language

■ (^) e ::= x | n | λx.e | e e | ref e | !e | e := e ■ (^) s ::= t | α.s ■ (^) t ::= α | int | t → t | ref t

• What if we try our standard rule?

Polymorphism and References

A ๵ e1 : t1 A,x: α.t ๵ e2 : t2 α A ๵ let x = e1 in e2 : t → →

CMSC 631, Fall 2003 61

• Example (due to Tofte)

let r = ref (λx.x) in // r : α.ref (α→α) r := λx.x+1; // checks; use r at ref (int → int) (!r) true // oops! checks; use r at ref(bool →bool)

• α should not be generalized, because later uses

of r may place constraints on it

• Nobody realized there was a problem for a long

time

Naive Generalization is Unsound

CMSC 631, Fall 2003 62

• Only allow values to be generalized

■ (^) v ::= x | n | λx.e ■ e ::= v | e e | ref e | !e | e := e ■ ■ ■ ■ ■ (^) Intuition: Values cannot later be updated ■ (^) This solution due to Wright and Felleisen

  • Tofte found a much more complicated solution

Solution: The Value Restriction

A ๵ v : t1 A,x: α.t ๵ e2 : t2 α A ๵ let x = v in e2 : t

• Handles higher-order functions

• Handles data structures smoothly

• Works in infinite domains

■ (^) Set of types is unlimited

• No forward/backward distinction

• Polymorphism provides context-sensitivity

Benefits of Type Inference

• Flow-insensitive

■ (^) Types are the same at all program points ■ (^) May produce coarse results ■ (^) Type inference failure can be hard to understand

• Polymorphism may not scale

■ Exponential in worst case ■ Seems fine in practice (witness ML)

Drawbacks to Type Inference