Hindley-Milner elaboration in applicative style
Fran¸cois Pottier
This pearl presents
a (shamefully) simple solution
to a problem that has (gently) troubled me for ten years and whosestory begins even longer ago.
This pearl presents
a (shamefully) simple solution
to a problem that has (gently) troubled me for ten years and whosestory begins even longer ago.
This pearl presents
a (shamefully) simple solution
to a problem that has (gently) troubled me for ten years
and whosestory begins even longer ago.
This pearl presents
a (shamefully) simple solution
to a problem that has (gently) troubled me for ten years and whosestory begins even longer ago.
Part I
A STORY
The 1970s
Milner (1978) invents MLpolymorphism andtype inference.
The 1970s
Milner (1978) invents MLpolymorphism andtype inference.
Milner’s description
Milner publishes a declarative presentation,Algorithm W,
and an imperative one, Algorithm J.
Algorithm J maintains a
“current substitution” in a global variableE.
Both composesubstitutions produced by unification, and create “new”variables as needed.
Milner’s description
Milner publishes a declarative presentation,Algorithm W,
and an imperative one, Algorithm J.
Algorithm J maintains a
“current substitution” in a global variableE.
Both composesubstitutions produced by unification, and create “new”variables as needed.
Milner’s description
Milner publishes a declarative presentation,Algorithm W, and an imperative one, Algorithm J.
Algorithm J maintains a
“current substitution” in a global variableE.
Both composesubstitutions produced by unification, and create “new”variables as needed.
Milner’s description
Milner publishes a declarative presentation,Algorithm W, and an imperative one, Algorithm J.
Algorithm J maintains a
“current substitution” in a global variableE.
Both composesubstitutions produced by unification, and create “new”variables as needed.
Milner’s description
Milner publishes a declarative presentation,Algorithm W, and an imperative one, Algorithm J.
Algorithm J maintains a
“current substitution” in a global variableE.
Both composesubstitutions produced by unification, and create “new”variables as needed.
Milner’s description
Milner publishes a declarative presentation,Algorithm W, and an imperative one, Algorithm J.
Algorithm J maintains a
“current substitution” in a global variableE.
Both composesubstitutions produced by unification, and create “new”variables as needed.
The 1980s
Cardelli, Wand (1987) and others formulate type inference as a two-stage process: generating andsolving a conjunction of equations.
The 1980s
Cardelli, Wand (1987) and others formulate type inference as a two-stage process: generating andsolving a conjunction of equations.
Benefits
Higher-level thinking:
instead ofsubstitutions and composition, equationsand conjunction.
Greater modularity:
constraints and constraint solving as a library, constraint generation performed by theuser.
Limitations
New variables still created via aglobal side effect.
Polymorphic type inferencenot supported.
Algorithm J must solvethe constraints produced so far (it looks up E) before it canproducemore constraints.
The 1990s
Kirchner & Jouannaud (1990), R´emy (1992) and others explain “new” variables asexistential quantificationand constraint solving asrewriting. A necessary step on the road towards explainingpolymorphicinference.
The 1990s
Kirchner & Jouannaud (1990), R´emy (1992) and others explain “new”
variables asexistential quantificationand constraint solving asrewriting.
A necessary step on the road towards explainingpolymorphicinference.
The 2000s
Following Gustavsson and Svenningsson (2001), Didier R´emy and F.P. (2005) explain polymorphic type inference usingconstraint abstractions.
The 2000s
Following Gustavsson and Svenningsson (2001), Didier R´emy and F.P.
(2005) explain polymorphic type inference usingconstraint abstractions.
Constraints
Constraints offer a syntax for describing type inference problems.
τ ::=α|τ →τ |. . .
C ::=false|true|C∧C|τ =τ | ∃α.C (unification)
|letx=λα.C inC (abstraction)
|x τ (application)
The meaning of let-constraints is given by the law:
letx=λα.C1 inC2
≡ ∃α.C1∧[λα.C1/x]C2
Constraint generation
A pure functionof a termt and a typeτ to a constraint Jt:τK. Jx:τK=x τ
Jλx.u:τK=∃α1α2.
τ =α1 →α2∧
letx=λα.(α=α1)inJu:α2K
Jt1t2 :τK=∃α.(Jt1:α→τK∧Jt2:αK) Jletx=t1 int2 :τK=letx=λα.Jt1 :αKinJt2 :τK
Constraint solving
On paper, every constraint can berewritten step by step to either false or a solved form.
The imperative implementation, based on Huet’s unification algorithm and R´emy’s ranks, is efficient(McAllester, 2003).
Library (OCaml)
Abstract syntax for constraints:
t y p e v a r i a b l e
val f r e s h : v a r i a b l e s t r u c t u r e o p t i o n - > v a r i a b l e t y p e r a w c o =
| C T r u e
| C C o n j of r a w c o * r a w c o
| CEq of v a r i a b l e * v a r i a b l e
| C E x i s t of v a r i a b l e * r a w c o
| ...
Combinators that build constraints:
val t r u t h : r a w c o
val ( ^ & ) : r a w c o - > r a w c o - > r a w c o
val ( - -) : v a r i a b l e - > v a r i a b l e - > r a w c o val e x i s t : ( v a r i a b l e - > r a w c o ) - > r a w c o ...
User (OCaml)
The user defines constraint generation:
let rec h a s t y p e ( t : ML . t e r m ) ( w : v a r i a b l e ) : r a w c o
= m a t c h t w i t h
| ...
| ML . Abs ( x , u ) - >
e x i s t ( fun v1 - >
e x i s t ( fun v2 - >
w - - - a r r o w v1 v2 ^&
def x v1 ( h a s t y p e u v2 ) )
)
| ...
let i s w e l l t y p e d ( t : ML . t e r m ) : r a w c o
= e x i s t ( fun w - > h a s t y p e t w )
Part II
A PROBLEM
A problem
Submitting aclosed ML term to the generator ...
yields aclosed constraint ... which the solver rewrites to ...
let b = if x = y then ... else ... in ...
∃α.(α=bool∧ ∃βγ.(. . .)) either false, or true.
A problem
Submitting aclosed ML term to the generator ...
yields aclosed constraint ...
which the solver rewrites to ...
let b = if x = y then ... else ... in ...
∃α.(α =bool∧ ∃βγ.(. . .))
either false, or true.
A problem
Submitting aclosed ML term to the generator ...
yields aclosed constraint ...
which the solver rewrites to ...
let b = if x = y then ... else ... in ...
∃α.(α =bool∧ ∃βγ.(. . .))
either false, or true.
A problem
Submitting aclosed ML term to the generator ...
yields aclosed constraint ...
which the solver rewrites to ...
let b = if x = y then ... else ... in ...
∃α.(α =bool∧ ∃βγ.(. . .)) either false, or true.
A problem (OCaml)
The API offered by the library istoo simple:
val s o l v e : r a w c o - > b o o l (Ignoring type error diagnostics.)
The user has defined: val i s w e l l t y p e d : ML . t e r m - > r a w c o
There is no way of obtaining, say:
val e l a b o r a t e : ML . t e r m - > F . t e r m
which would be the front-end of atype-directed compiler.
A problem (OCaml)
The API offered by the library istoo simple:
val s o l v e : r a w c o - > b o o l
(Ignoring type error diagnostics.) The user has defined:
val i s w e l l t y p e d : ML . t e r m - > r a w c o
There is no way of obtaining, say:
val e l a b o r a t e : ML . t e r m - > F . t e r m
which would be the front-end of atype-directed compiler.
A problem (OCaml)
The API offered by the library istoo simple:
val s o l v e : r a w c o - > b o o l
(Ignoring type error diagnostics.) The user has defined:
val i s w e l l t y p e d : ML . t e r m - > r a w c o There is no way of obtaining, say:
val e l a b o r a t e : ML . t e r m - > F . t e r m
which would be the front-end of atype-directed compiler.
Question
Can one performelaboration
without compromising themodularity andelegance of the constraint-based approach?
Part III
A SOLUTION
A low-level solution
The generator could produce a pair of a constraintand
a template for an elaborated term, sharingmutable placeholders for evidence, so that, after the constraint issolved,
the template can be“solidified”into an elaborated term.
Library, low-level (OCaml)
Constraints already contain mutable placeholders for evidence:
... | C E x i s t of v a r i a b l e * r a w c o | ...
More placeholders (not shown) required to deal with polymorphism.
Let the library offer a type decoder, which can be invokedaftersolving:
t y p e d e c o d e r = v a r i a b l e - > ty val n e w _ d e c o d e r : u n i t - > d e c o d e r ...
User (OCaml)
The user could write:
val h a s t y p e :
ML . t e r m - > v a r i a b l e - > r a w c o * F . t e m p l a t e val s o l i d i f y :
F . t e m p l a t e - > F . t e r m where:
the constraint and the template sharevariables,
solidifyuses a type decoder to replace these variables with types.
Why I not am happy with stopping here
This approach is in three stages: generation, solving, solidification.
Each user construct is dealt withtwice,in stages 1 and 3.
This approachexposes evidenceto the user.
Evidence is mutable and involves names and binders.
One needs an intermediate representationF.template, or one must polluteF.term.
A wish
Even though stages 1 and 3 must beexecutedseparately, the user would prefer todescribethem in a unified manner.
A dream
If the user could somehow (magically?)
construct the constraint, and “simultaneously”
query the solver for thefinal (decoded) witness for a variable then she would be able to perform elaboration in one swoop:
val e l a b o r a t e : ML . t e r m - > F . t e r m and evidence would not need to be exposed.
The idea
Give the user theillusionthat she can use the solver in this manner.
Give her a DSL to expresscomputationsthat:
emit constraintsand read their solutions.
It turns out that this DSL is just our good oldconstraints,
extended with a mapcombinator.
The idea
Give the user theillusionthat she can use the solver in this manner.
Give her a DSL to expresscomputationsthat:
emit constraintsand read their solutions.
It turns out that this DSL is just our good oldconstraints,
extended with a mapcombinator.
Library, high-level (OCaml)
Solving/evaluating a constraintproduces a result.
t y p e ’ a co
val s o l v e : ’ a co - > ’ a val p u r e : ’ a - > ’ a co
val ( ^ & ) : ’ a co - > ’ b co - > ( ’ a * ’ b ) co val map : ( ’ a - > ’ b ) - > ’ a co - > ’ b co val ( - -): v a r i a b l e - > v a r i a b l e - > u n i t co val e x i s t : ( v a r i a b l e - > ’ a co ) - > ( ty * ’ a ) co ...
E.g., evaluating∃α.C yields apairof a decoded type (thewitness forα) and the value ofC.
Library, high-level (OCaml)
This is implementedon top ofthe earlier, low-level library.
t y p e env = d e c o d e r t y p e ’ a co =
r a w c o * ( env - > ’ a )
A constraint/computation is a pair of
I a raw constraint, which contains mutable evidence;
I a continuation,which reads this evidenceafterthe solver has run.
Library, high-level (OCaml)
The implementation is quasi-trivial.
let e x i s t f =
let v = f r e s h N o n e in let rc , k = f v in C E x i s t ( v , rc ) , fun env - >
let d e c o d e = env in ( d e c o d e v , k env )
User (OCaml)
The user defines inference/elaboration inone inductive function:
let rec h a s t y p e t w : F.term co
= m a t c h t w i t h
| ...
| ML . Abs ( x , u ) - >
e x i s t ( fun v1 - >
e x i s t ( fun v2 - >
w - - - a r r o w v1 v2 ^&
def x v1 ( h a s t y p e u v2 ) )
) <$$> fun (ty1, (ty2, ((), u’))) ->
F.Abs (x, ty1, u’)
| ...
The (final, decoded) typety1 of x seems to bemagically available.
Remarks
Elaboration from ML to System F in the paper (and online).
The type’a co forms an applicative functor,not a monad.
Part IV
Conclusion
Conclusion
I a simple idea, really
I just icing on the cake
I modularity, elegance, performance
I usable in other settings? e.g. higher-order pattern unification?
Thank you
http://gallium.inria.fr/~fpottier/inferno/
No mutable state was exposed in the making of this library.