Parametric Type Inferencing for Helium Bastiaan Heeren and Jurriaan - - PowerPoint PPT Presentation

parametric type inferencing for helium bastiaan heeren
SMART_READER_LITE
LIVE PREVIEW

Parametric Type Inferencing for Helium Bastiaan Heeren and Jurriaan - - PowerPoint PPT Presentation

Parametric Type Inferencing for Helium Bastiaan Heeren and Jurriaan Hage Institute of Information and Computing Science Utrecht University e-mail: { bastiaan,jur } @cs.uu.nl September 17, 2002 The Helium


slide-1
SLIDE 1

◭ ◭ ◭ ◮ ◮ ◮ ◭

  • ×

Parametric Type Inferencing for Helium Bastiaan Heeren and Jurriaan Hage

Institute of Information and Computing Science Utrecht University e-mail: {bastiaan,jur}@cs.uu.nl September 17, 2002

slide-2
SLIDE 2

1 ◭ ◭ ◭ ◮ ◮ ◮ ◭

  • ×

The Helium Compiler

◮ Helium is Haskell 98 with the following restrictions: type classes are not allowed, all type variables have kind ⋆. ◮ A major design criterion is the ability to give superb error messages. This is especially needful for novice functional programmers. ◮ The compiler is structured as follows.

Scanning and Parsing Static Analysis

UHA UHA .hs

Parsec UU_AG

.core lvmToBytes coreToAsm asmToLvm

The LVM assembler library Desugaring

slide-3
SLIDE 3

2 ◭ ◭ ◭ ◮ ◮ ◮ ◭

  • ×

Types and Type Constraints (1)

◮ The syntax of types and type schemes is given by: (type) τ := α | T τ1 . . . τn where arity(T) = n (type scheme) σ := ∀ α.τ ◮ We introduce three forms of type constraint: (constraint) C := τ1 ≡ τ2 (equality) | τ1 M τ2 (implicit instance) | τ σ (explicit instance)

slide-4
SLIDE 4

3 ◭ ◭ ◭ ◮ ◮ ◮ ◭

  • ×

Types and Type Constraints (2)

◮ Satisfaction of a constraint by a substitution S is defined as follows. S satisfies (τ1 ≡ τ2) =def Sτ1 = Sτ2 S satisfies (τ1 M τ2) =def Sτ1 ≺ generalize(SM, Sτ2) S satisfies (τ σ) =def Sτ ≺ Sσ ◮ Examples: (τ3 ∅ τ1 → τ2) is satisfied by S =[τ1 := τ2, τ3 := Int → Int] Sτ3 ≺ generalize(S(∅), S(τ1 → τ2)) Int → Int ≺ generalize(∅, (τ2 → τ2)) Int → Int ≺ ∀α.α → α (τ3 {τ4} τ1 → τ2) isn’t satisfied by S =[τ1 := τ4, τ2 := τ4, τ3 := Int → Int] Sτ3 ≺ generalize(S{τ4}, S(τ1 → τ2)) Int → Int ≺ generalize({τ4}, τ4 → τ4) Int → Int ≺ τ4 → τ4

slide-5
SLIDE 5

4 ◭ ◭ ◭ ◮ ◮ ◮ ◭

  • ×

Bottom-Up Typing Rules

A judgement M, A, C ⊢

e BU e : τ for expression e consists of:

◮ a set of monomorphic type variables M, ◮ an assumption set A to record the type variables for the free variables, ◮ a set of type constraints C, ◮ a type τ for e.

M, {x:β}, ∅ ⊢

e BU x : β

[Var]e

BU

M, A1, C1 ⊢

e BU e1 : τ1

M, A2, C2 ⊢

e BU e2 : τ2

M, A3, C3 ⊢

e BU e3 : τ3

M, A1 ∪ A2 ∪ A3, C1 ∪ C2 ∪ C3 ∪ {τ1 ≡ Bool, τ2 ≡ β, τ3 ≡ β} ⊢

e BU if e1 then e2 else e3 : β

[If]e

BU

Bi, Ci ⊢

p BU pi : τi for 1 ≤ i ≤ n

B =

i Bi

M ∪ ran(B), A, C ⊢

e BU e : τ

M, A\dom(B),

i Ci ∪ C ∪ (B ≡ A) ∪ {β ≡ τ1 → . . . → τn → τ} ⊢ e BU (λp1 . . . pn → e) : β

[Abs]e

BU

slide-6
SLIDE 6

5 ◭ ◭ ◭ ◮ ◮ ◮ ◭

  • ×

Collecting the Constraints (1)

The attributes for an expression are given by: ATTR Expr [ mono : Types (inherited) | unique : Int (chained) | aset : Assumptions (synthesized) ctree : ConstraintTree beta : Type ] ◮ The type constraints are stored in a tree. Later, these can be ordered. ◮ The unique counter provides fresh type variables when required.

slide-7
SLIDE 7

6 ◭ ◭ ◭ ◮ ◮ ◮ ◭

  • ×

Collecting the Constraints (2)

u m u u m u u m u u m u b b b b then lhs

If Expr Expr

else guard

Expr

c c c c a a a a

SEM Expr | If

slide-8
SLIDE 8

6 ◭ ◭ ◭ ◮ ◮ ◮ ◭

  • ×

Collecting the Constraints (2)

u m u u m u u m u u m u b b b b then lhs

If Expr Expr

else guard

Expr

loc b c c c c a a a a

SEM Expr | If loc . beta = TVar @lhs.unique

slide-9
SLIDE 9

6 ◭ ◭ ◭ ◮ ◮ ◮ ◭

  • ×

Collecting the Constraints (2)

u m u u m u u m u u m u b b b b then lhs

If Expr Expr

else guard

Expr

loc b c c c c a a a a

SEM Expr | If loc . beta = TVar @lhs.unique guard . unique = @lhs.unique + 1

slide-10
SLIDE 10

6 ◭ ◭ ◭ ◮ ◮ ◮ ◭

  • ×

Collecting the Constraints (2)

u m u u m u u m u u m u b b b b then lhs

If Expr Expr

else guard

Expr

loc b c c c c a a a a

SEM Expr | If loc . beta = TVar @lhs.unique guard . unique = @lhs.unique + 1 lhs . aset = @guard.aset + + @then.aset + + @else.aset

slide-11
SLIDE 11

6 ◭ ◭ ◭ ◮ ◮ ◮ ◭

  • ×

Collecting the Constraints (2)

u u m u u m u u m u u m guard b b b b then lhs

If Expr Expr

else

Expr

loc b c c c c a a a a

SEM Expr | If loc . beta = TVar @lhs.unique guard . unique = @lhs.unique + 1 lhs . aset = @guard.aset + + @then.aset + + @else.aset . ctree = Node [ [@guard.beta ≡ boolType] ‘add‘ @guard.ctree , [@then.beta ≡ @beta] ‘add‘ @then.ctree , [@else.beta ≡ @beta] ‘add‘ @else.ctree ]

slide-12
SLIDE 12

6 ◭ ◭ ◭ ◮ ◮ ◮ ◭

  • ×

Collecting the Constraints (2)

u u m u u m u u m u m guard u b b b b then lhs

If Expr Expr

else

Expr

loc b c c c c a a a a

SEM Expr | If loc . beta = TVar @lhs.unique guard . unique = @lhs.unique + 1 lhs . aset = @guard.aset + + @then.aset + + @else.aset . ctree = Node [ [@guard.beta ≡ boolType] ‘add‘ @guard.ctree , [@then.beta ≡ @beta] ‘add‘ @then.ctree , [@else.beta ≡ @beta] ‘add‘ @else.ctree ]

slide-13
SLIDE 13

7 ◭ ◭ ◭ ◮ ◮ ◮ ◭

  • ×

Collecting the Constraints (3)

Bi, Ci ⊢

p BU pi : τi for 1 ≤ i ≤ n

B =

i Bi

M ∪ ran(B), A, C ⊢

e BU e : τ

M, A\dom(B),

i Ci ∪ C ∪ (B ≡ A) ∪ {β ≡ τ1 → . . . → τn → τ} ⊢ e BU (λp1 . . . pn → e) : β

[Abs]e

BU

SEM Expression | Lambda lhs . aset = removeKeys (map fst @pats.bset) @expr.aset . ctree = [ beta ≡ foldr (→) @expr.beta @pats.betas ] ‘add‘ Node [ @pats.ctree, @binds ‘spread‘ @expr.ctree ] pats . unique = @lhs.unique + 1 expr . mono = map snd @pats.bset + + @lhs.mono loc . beta = TVar @lhs.unique . binds = [ τ1 ≡ τ2 | (x1,τ1) ← @pats.bset , (x2,τ2) ← @expr.aset , x1 == x2 ]

slide-14
SLIDE 14

8 ◭ ◭ ◭ ◮ ◮ ◮ ◭

  • ×

Flattening the Constraint Tree

◮ The location where an inconsistency is detected strongly depends on the order in which types are unified ◮ By flattening the tree into a list of constraints to be considered in that order, we can imitate type inference algorithms such as W and M ◮ Type constraints corresponding to the binding of a variable to a pat- tern variable can be “spread” to the location of the bound variable

W spread + postorder M spread + preorder Bottom-Up no spread + postorder

v6 v2

Var "f" Var "x" Abs App Var "f" App Var "f" Var "x"

v0 v1 v7 v7 = v0 -> v1 -> v6 v0 = v3 v1 = v4 v2 = v5 -> v6 v3 = v4 -> v5 v5 v4 v3 v0 = v2

slide-15
SLIDE 15

9 ◭ ◭ ◭ ◮ ◮ ◮ ◭

  • ×

A Type Class to Solve Constraints (1)

class Solver solver info where initialize :: State solver info () makeConsistent :: State solver info () unifyTypes :: info → Type → Type → State solver info () newVariables :: [Int] → State solver info () findSubstForVar :: Int → State solver info Type ◮ The State monad contains a uniqueness counter, a list of reported inconsistencies, and a substitution or something equivalent. ◮ By default, initialize, makeConsistent, and newVariables do nothing. solve :: Solver solver info ⇒ Int → Constraints info → State solver info () solve unique constraints = do setUnique unique initialize mapM solveOne constraints makeConsistent

slide-16
SLIDE 16

10 ◭ ◭ ◭ ◮ ◮ ◮ ◭

  • ×

A Type Class to Solve Constraints (2)

◮ The following code fragment shows how to solve a single constraint. solveOne :: Solver solver info ⇒ Constraint info → State solver info () solveOne constraint = case constraint of t1 ≡ t2 → do unifyTypes info t1 t2 tp ts → do unique ← getUnique let (unique’, its) = instantiate unique ts setUnique unique’ newVariables [unique..unique’-1] solveOne (tp ≡ its) t1 m t2 → do makeConsistent t2’ ← applySubst t2 m’ ← mapM applySubst m let scheme = generalize (ftv m’) t2’ solveOne (t1 scheme)

slide-17
SLIDE 17

11 ◭ ◭ ◭ ◮ ◮ ◮ ◭

  • ×

Greedy Constraint Solving

The most obvious instance of Solver is a substitution, such that: ◮ unifyTypes incorporates the most general unifier of the two types into the substitution. ◮ findSubstForVar applies the current substitution to a type variable. ◮ If two types cannot be unified, we immediately deal with the inconsistency. An appropriate error message is constructed that explains why the types cannot be unified. Information that is stored with the constraint can be used. We can choose to ignore the erroneous constraint and continue solving the remaining constraints. This might lead to the detection of multiple errors. ◮ The default skip function is the obvious choice for makeConsistent.

slide-18
SLIDE 18

12 ◭ ◭ ◭ ◮ ◮ ◮ ◭

  • ×

Constraint Solving with Type Graphs (1)

◮ A type graph considers a complete set of type constraints. Compared to the standard (greedy) algorithms, the advantages are the following. There is no left to right bias in the algorithm. Global analysis can be performed, since all deductive steps for a type variable are available. It is straightforward to plug-in your own heuristics. ◮ The relative order of the implicit instance constraints must be taken into

  • account. Moreover, when converting an implicit instance constraint into an

explicit instance constraint, the state must be consistent. ◮ Example: f 0 y = y f x y = if y then x else f (x − 1) y

slide-19
SLIDE 19

13 ◭ ◭ ◭ ◮ ◮ ◮ ◭

  • ×

Constraint Solving with Type Graphs (2)

v14 v15 v2 v16 v7 v11 v12 v9 v1 v6 v5 v3 v19 v8 v10 v18 v17 v13 v4 v0

#17 #14 #16 #18 #3 #2 #15 #7 #11 #12 #22 #21 #6 #5 #4 #8 #19 #9 #10 #13 #20 #1 (1) (2) (1) (2) (2) (1) (2) (1) (1) (1) (2) (2) (2) (1) (1) (2)

Int

  • >
  • >

Int Int

  • >
  • >

Int Bool

  • >
  • >
  • >
  • >

Int

slide-20
SLIDE 20

14 ◭ ◭ ◭ ◮ ◮ ◮ ◭

  • ×

Constraint Solving with Type Graphs (3)

v14 v15 v2 v16 v7 v11 v12 v9 v1 v6 v5 v3 v19 v8 v10 v4

#18 #3 #2 #15 #7 #11 #12 #22 #21 #6 #5 #4 #8 #19 #9 #10

Int Int Int Bool

f 0 y = y f x y = if y then x else f (x − 1) y

slide-21
SLIDE 21

14 ◭ ◭ ◭ ◮ ◮ ◮ ◭

  • ×

Constraint Solving with Type Graphs (3)

#18 #3 #2 #15 #7 #11 #12 #22 #21 #6 #5 #4 #8 #19 #9 #10

y y y y y f (x-1) y x x x x-1 res (-) if-expr

Bool Int Int Int

1st arg snd arg rhs

f 0 y = y f x y = if y then x else f (x − 1) y

slide-22
SLIDE 22

14 ◭ ◭ ◭ ◮ ◮ ◮ ◭

  • ×

Constraint Solving with Type Graphs (3)

y y

#18 #3 #2 #15 #7 #11 #12 #22 #21 #6 #5 #4 #8 #19 #9 #10

y x-1 res (-) y y f (x-1) y x x x rhs if-expr

Bool Int Int Int

1st arg snd arg

f 0 y = y f x y = if y then x else f (x − 1) y

Hugs (December 2001)

ERROR "Test.hs":2 - Type error in conditional *** Expression : if y then x else f (x - 1) y *** Term : f (x - 1) y *** Type : Bool *** Does not match : Int

slide-23
SLIDE 23

14 ◭ ◭ ◭ ◮ ◮ ◮ ◭

  • ×

Constraint Solving with Type Graphs (3)

y y

#18 #3 #2 #15 #7 #11 #12 #22 #21 #6 #5 #4 #8 #19 #9 #10

y x-1 res (-) y y f (x-1) y x x x rhs if-expr

Bool Int Int Int

1st arg snd arg

f 0 y = y f x y = if y then x else f (x − 1) y

GHC (version 5.02.3)

Test.hs:2: Couldn’t match ‘Bool’ against ‘Int’ Expected type: Bool Inferred type: Int In the definition of ‘f’: if y then x else f (x - 1) y

slide-24
SLIDE 24

14 ◭ ◭ ◭ ◮ ◮ ◮ ◭

  • ×

Constraint Solving with Type Graphs (3)

y y

#18 #3 #2 #15 #7 #11 #12 #22 #21 #6 #5 #4 #8 #19 #9 #10

y x-1 res (-) y y f (x-1) y x x x rhs if-expr

Bool Int Int Int

1st arg snd arg

f 0 y = y f x y = if y then x else f (x − 1) y

Alternative message 1

(2,12): Type error in conditional Expression : if y then x else f (x - 1) y Term : y Type : Int Does not match : Bool

slide-25
SLIDE 25

14 ◭ ◭ ◭ ◮ ◮ ◮ ◭

  • ×

Constraint Solving with Type Graphs (3)

y y

#18 #3 #2 #15 #7 #11 #12 #22 #21 #6 #5 #4 #8 #19 #9 #10

y x-1 res (-) y y f (x-1) y x x x rhs if-expr

Bool Int Int Int

1st arg snd arg

f 0 y = y f x y = if y then x else f (x − 1) y

Alternative message 2

(1,9): Type error in rhs of function binding Expression : y Type : Bool Does not match : Int

slide-26
SLIDE 26

14 ◭ ◭ ◭ ◮ ◮ ◮ ◭

  • ×

Constraint Solving with Type Graphs (3)

y y

#18 #3 #2 #15 #7 #11 #12 #22 #21 #6 #5 #4 #8 #19 #9 #10

y x-1 res (-) y y f (x-1) y x x x rhs if-expr

Bool Int Int Int

1st arg snd arg

f 0 y = y f x y = if y then x else f (x − 1) y

Alternative message 3

(2,19): Type error in conditional Expression : if y then x else f (x - 1) y Term : x Type : Int Does not match : Bool

slide-27
SLIDE 27

15 ◭ ◭ ◭ ◮ ◮ ◮ ◭

  • ×

Conclusion and Future Work

◮ We have shown a type inference algorithm that is based on collecting and satisfying type constraints. Typically, the necessary information is passed bottom-up. ◮ The generic implementation paves the way for experimentation with various methods of type inferencing. ◮ Solving the constraints with a type graph removes the left to right bias inherent in most type inference algorithms. Furthermore, it allows for global analysis as well as the application of several heuristics. ◮ This year, we plan to use the Helium compiler in a concrete educational setting.