590 likes | 702 Views
This paper explores the challenges and strategies involved in migrating programs from dynamically-typed to statically-typed languages. It leverages run-time logs to facilitate type inference, aiming to minimize the programmer's burden during migration. By analyzing run-time executions, we aim to deduce valid types while maintaining the flexibility of dynamic languages. Our approach emphasizes practical type systems that support polymorphism and bounded quantification, providing hope for the evolution of existing codebases with minimal modifications.
E N D
Type Inferencewith Run-time Logs Ravi Chugh, Ranjit Jhala, Sorin Lerner University of California, San Diego
Motivation • Dynamically-typed languages • Rapid prototyping • Facilitate inter-language development • Statically-typed languages • Prevent certain run-time errors • Enable optimized execution • Provide checked documentation • Gradual type systems offer a spectrum • Provides hope for evolving existing programs • But …
... We Need Inference! • Goal: migrate programs from dynamic langs • Programmer annotation burden is currently 0 • A migration strategy must require ~0 work • Even modifying 1-10% LOC in a large codebasecan be prohibitive
The Challenge: Inference Goal: practical type system polymorphism def id(x) {return x}; 1 + id(2); “hello” ^ id(“world”); id : ∀X. X → X polymorphism
The Challenge: Inference Goal: practical type system subtyping def succA(x) { return (1 + x.a)}; succA({a=1}); succA({a=1;b=“hi”}); subtyping polymorphism {a:Int;b:Str}<:{a:Int}
The Challenge: Inference Goal: practical type system bounded quantification def incA(x) { x.a := 1 + x.a; return x}; incA({a=1}).a; incA({a=1;b=“hi”}).b; bounded quantification subtyping polymorphism incA:∀X<:{a:Int}.X→X
The Challenge: Inference Goal: practical type system dynamic n := if b then 0 else “bad”; m := if b then n + 1 else 0; n : dynamic bounded quantification subtyping polymorphism dynamic
The Challenge: Inference Goal: practical type system bounded quantification subtyping polymorphism other features... dynamic
The Challenge: Inference Goal: practical type system bounded quantification subtyping polymorphism other features... dynamic System ML ✓
The Challenge: Inference Goal: practical type system bounded quantification subtyping polymorphism other features... dynamic System F ✗
The Idea • Use run-time executions to help inference • Program may have many valid types • But particular execution might rule out some • Dynamic language programmers test a lot • Use existing test suites to help migration
Route: Inference w/ Run-time Logs omit higher-order functions System E≤ System E bounded quantification subtyping System E− + polymorphism
First Stop: System E− omit higher-order functions System E≤ System E bounded quantification subtyping System E− + polymorphism
E− Type System Expression and function types τ ::= | Int | Bool | ... | {fi:τi} | X σ ::= ∀Xi. τ1→ τ2 Typing rules prevent field-not-found and primitive operation errors
def id (a) { a } def id[A] (a:A) { a } : ∀A. A → A def id[] (a:Int) { a } : Int → Int def id[B,C] (x:B*C) { a } : ∀B,C. B*C → B*C Infinitely many valid types for id...
Int → Int ∀B,C. B*C → B*C ∀A. A → A ... but ∀A. A → A is the principal type
Int → Int ∀B,C. B*C → B*C ∀A. A → A ∀B. B*B → B*B Int*Bool → Int*Bool Int*Int → Int*Int ... but ∀A. A → A is the principal type Allows id to be used in most different ways
∀F. {f:F} → F {f:Int} → Int ∀F,G. {f:F;g:G} → F ∀G. {f:Int;g:G} → Int def readF (o) { o.f } def readF[F] (o:{f:F}) { o.f } : ∀F. {f:F} → F This is the best type
∀F.{f:F}→{f:F} ∀F,G.{f:F;g:G}→{f:F;g:G} allows hasF({f=1}) allows hasF({f=1;g=2}).g def hasF (x) { let _ = x.f in x } Two valid types: Neither is better than the other
E− Static Type Inference • E− lacks principal types • Cannot assign type just from definition • Need to consider calling contexts • Our approach is iterative • Impose minimal constraints on argument • If a calling context requires an additional field to be tracked, backtrack and redo the function
Iterative Inference – Example 1 def hasF(x) { let _ = x.f in x } def main1() { hasF({f=1}) } ✓ Constraints on x Solution X = {f:F}x X <: {f:F} “this record type came from the program variable x” hasF:∀F.{f:F} → {f:F}x 21
Iterative Inference – Example 2 ✗* def hasF(x) { let _ = x.f in x } def main2() { let z = {f=1;g=2} in hasF(z).g } hasF(z):{f:Int}x so can’t project on g, unless... Constraints on x Solution X = {f:F}x X <: {f:F} X <: {g:G} hasF:∀F.{f:F} → {f:F}x 22
Iterative Inference – Example 2 def hasF(x) { let _ = x.f in x } def main2() { let z = {f=1;g=2} in hasF(z).g } ✓ Constraints on x Solution X={f:F;g:G}x X <: {f:F} X <: {g:G} hasF:∀F,G.{f:F;g:G} → {f:F;g:G}x 23
Iterative Inference – Example 3 def hasF(x) { let _ = x.f in x } def main3() { let z = {f=1;g=2} in hasF(z).g; hasF({f=1}) } Constraints on x Solution X={f:F;g:G}x X <: {f:F} X <: {g:G} hasF:∀F,G.{f:F;g:G} → {f:F;g:G}x 24
Iterative Inference – Example 3 def hasF(x) { let _ = x.f in x } def main3() { let z = {f=1;g=2} in hasF(z).g; hasF({f=1}) } ✓ ✗ Constraints on x Solution X={f:F;g:G}x X <: {f:F} X <: {g:G} hasF:∀F,G.{f:F;g:G} → {f:F;g:G}x 25
E− Type Inference with Run-time Logs • Evaluation can log caller-induced constraints • Wrap all values with sets of type variables • When a value passed to arg x, add Xx tag • When a value with tag Xx projected on field f, record Xx <: {f : Xx.f} • Modified inference finds all caller-induced constraints in run-time log 26
def hasF(x) { let _ = x.f in x } def main2() { let z = {f=1;g=2} in hasF(z).g } Evaluation main2() ⇒ let z = {f=1;g=2} in hasF(z).g ⇒ let z = [{f=1;g=2},{}] in hasF(z).g ⇒ hasF([{f=1;g=2},{Xx}]).g ⇒ (let _ = [{f=1;g=2},{Xx}].f in [{f=1;g=2},{Xx}]).g ⇒ (let _ = [1,{Xx.f}] in [{f=1;g=2},{Xx}]).g ⇒ [{f=1;g=2},{Xx}].g ⇒ [2,{Xx.g}] Run-time log Xx <: {f : Xx.f} Caller-inducedconstraint Xx <: {g : Xx.g} 27
System E− Summary • Fully static inference needs to iterate, but can optimize with run-time information • If all expressions executed, then • log contains all caller-induced constraints • no need for iteration
Next Stop: System E System E≤ System E System E bounded quantification subtyping System E− + polymorphism
if b then 1 else 2 : Int if b then 1 else true : Top if b then {f=1; g=“”} else {f=2; h=true} : {f:Int} if b then {f=“”} else {f=true} : {f:Top} If-expressions Type is a supertype of branch types
∀A.{n:Int}*Top→Top ∀A.{n:Int}*{n:Top}→{n:Top} {n:Int}*{n:Int}→{n:Int} ∀B.{n:Int;b:B}*{n:Int;b:B}→{n:Int;b:B} ∀B.{n:Int;b:B}*{b:B}→{b:B} def choose (y,z) { if y.n > 0 then y else z } Several incomparable types:
“this type came from theif-expression 1” Iterative Inference – Example def choose(y,z) { if1 y.n > 0 then y else z } def main4() { let t = choose({n=1},{n=2}) in succ t.n } Constraints Y <: {n:N} N = Int choose: {n:Int}*Top → Top1 Iteration 0 32 32
“this value came from the n field of the record returned by if-expression 1” Iterative Inference – Example def choose(y,z) { if1 y.n > 0 then y else z } def main4() { let t = choose({n=1},{n=2}) in succ t.n } ✗* t:Top1 Constraints Y <: {n:N} N = Int Z <: {n:M} choose: {n:Int}*Top → Top1 Iteration 0 choose: {n:Int}*{n:Top} → {n:Top1.n}1 Iteration 1 33 33
Iterative Inference – Example def choose(y,z) { if1 y.n > 0 then y else z } def main4() { let t = choose({n=1},{n=2}) in succ t.n } ✗* t.n:Top1.n Constraints Y <: {n:N} N = Int Z <: {n:M} N = M choose: {n:Int}*Top → Top1 Iteration 0 choose: {n:Int}*{n:Top} → {n:Top1.n}1 Iteration 1 choose: {n:Int}*{n:Int} → {n:Int}1 Iteration 2 34 34
Iterative Inference – Example def choose(y,z) { if1 y.n > 0 then y else z } def main4() { let t = choose({n=1},{n=2}) in succ t.n } ✓ Constraints Y <: {n:N} N = Int Z <: {n:M} N = M choose: {n:Int}*Top → Top1 Iteration 0 choose: {n:Int}*{n:Top} → {n:Top1.n}1 Iteration 1 choose: {n:Int}*{n:Int} → {n:Int}1 Iteration 2 35 35
System E Summary • Lacks principal types • Has iterative inference • Subscripts for types returned by if-exps • Subscripts for Top in case it reaches a primop • Can remove iteration with run-time info
Last Stop: System E≤ System E≤ System E≤ System E bounded quantification subtyping System E− + polymorphism
Bounded Quantification def f[ X1<:τ1, ... , Xn<:τn ] (x:τ) { e }
Bounded Quantification def f[ X1<:τ1 , ... , Xn<:τn ] (x:τ) { e } Type parameters now have bounds 39
∀F, X<:{f:F}.X→X ∀F.{f:F}→{f:F} ∀F,G.{f:F;g:G}→{f:F;g:G} def hasF (x) { let _ = x.f in x } Now there is a best type for hasF Each call site can instantiate X differently 40
{n:Int}*Top→Top {n:Int}*{n:Top}→{n:Top} {n:Int}*{n:Int}→{n:Int} ∀B.{n:Int;b:B}*{n:Int;b:B}→{n:Int;b:B} ∀B.{n:Int;b:B}*{b:B}→{b:B} ∀Y<:{n:Int}. Y*Y→Y def choose (y,z) { if1 y.n > 0 then y else z }
∀Y<:{n:Int}. Y*Y→Y {n:Int}*Top→Top {n:Int}*{n:Top}→{n:Top} • These four types are incomparable ∀B.{n:Int;b:B}*{b:B}→{b:B} def choose (y,z) { if1 y.n > 0 then y else z } 42
choose: ∀Y<:{n:Int}. Y * Y → Y 0 def choose(y,z) { if1 y.n > 0 then y else z } def main5() { let _ = choose({n=1;b=tru},{n=2;b=fls}) in let w = choose({n=1;b=tru},{b=fls}) in not w.b } Constraints Y<:{n:N} N=Int Y=Z 43 43
def choose(y,z) { if1 y.n > 0 then y else z } def main5() { let _ = choose({n=1;b=tru},{n=2;b=fls}) in let w = choose({n=1;b=tru},{b=fls}) in not w.b } ✓ ✗* Constraints Y<:{n:N} N=Int Y=Z choose: ∀Y<:{n:Int}. Y * Y → Y 0 44 44
✗* choose: {n:Int}*Top → Top1 1 def choose(y,z) { if1 y.n > 0 then y else z } def main5() { let _ = choose({n=1;b=tru},{n=2;b=fls}) in let w = choose({n=1;b=tru},{b=fls}) in not w.b } Constraints Y<:{n:N} N=Int Y≠Z choose: ∀Y<:{n:Int}. Y * Y → Y 0 45 45
✓ ✓ choose: {n:Int;b:Top}*{b:Top} → {b:Top1.b}1 2 def choose(y,z) { if1 y.n > 0 then y else z } def main5() { let _ = choose({n=1;b=tru},{n=2;b=fls}) in let w = choose({n=1;b=tru},{b=fls}) in not w.b } ✗* w:Top1 Constraints Y<:{n:N} N=Int Y≠Z Y<:{b:B} Z<:{b:C} choose: ∀Y<:{n:Int}. Y * Y → Y 0 choose: {n:Int}*Top → Top1 1 46 46
✓ ✓ choose: ∀B.{n:Int;b:B}*{b:B}→{b:B} 3 def choose(y,z) { if1 y.n > 0 then y else z } def main5() { let _ = choose({n=1;b=tru},{n=2;b=fls}) in let w = choose({n=1;b=tru},{b=fls}) in not w.b } ✗* w.b:Top1.b Constraints Y<:{n:N} N=Int Y≠Z Y<:{b:B} Z<:{b:C} B=C choose: ∀Y<:{n:Int}. Y * Y → Y 0 choose: {n:Int}*Top → Top1 1 choose: {n:Int;b:Top}*{b:Top} → {b:Top1.b}1 2 47 47
def choose(y,z) { if1 y.n > 0 then y else z } def main5() { let _ = choose({n=1;b=tru},{n=2;b=fls}) in let w = choose({n=1;b=tru},{b=fls}) in not w.b } ✓ Constraints Y<:{n:N} N=Int Y≠Z Y<:{b:B} Z<:{b:C} B=C choose: ∀Y<:{n:Int}. Y * Y → Y 0 choose: {n:Int}*Top → Top1 1 choose: {n:Int;b:Top}*{b:Top} → {b:Top1.b}1 2 choose: ∀B.{n:Int;b:B}*{b:B}→{b:B} 3 48 48
System E≤ Summary • Lacks principal types • When to share variables: Y=Z or Y≠Z? • New source of iteration for static inference • Can partially eliminate with run-time info • When Y≠Z, same as System E
Summary System E≤ System E bounded quantification subtyping System E− + polymorphism