810 likes | 907 Views
Type Inference with Run-time Logs. Ravi Chugh. Motivation: Dynamic Languages. Dynamically-typed languages Enable rapid prototyping Facilitate inter-language development Statically-typed languages Prevent certain run-time errors Enable optimized execution Provide checked documentation
E N D
Type Inferencewith Run-time Logs Ravi Chugh
Motivation: Dynamic Languages • Dynamically-typed languages • Enable rapid prototyping • Facilitate inter-language development • Statically-typed languages • Prevent certain run-time errors • Enable optimized execution • Provide checked documentation • Many research efforts to combine both • Recent popularity of Python, Ruby, and JavaScript has stoked the fire
Static Type Systems • Many attempts at fully static type systems • Flow sensitive types, occurrence types, ... • Often require refactoring and annotation • Some features just cannot be statically typed • Retrofitting existing language unlikely • Could work for a new language • But we want to migrate existing programs
Gradual Type Systems • Typing is not all-or-nothing • Some expressions have types, others are dynamic • Typed portions checked in standard ways • Untyped portions fall back on run-time checks • Challenges • Granularity • Guarantees • Blame tracking • and ...
... 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 dynamic other features...
The Challenge: Inference Goal: practical type system bounded quantification subtyping polymorphism dynamic other features... System ML ✓
The Challenge: Inference Goal: practical type system bounded quantification subtyping polymorphism dynamic other features... 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 + polymorphism
First Stop omit higher-order functions System E≤ System E bounded quantification subtyping System E− + polymorphism
Typed vs. Untyped Syntax def y[A1,...,An](x:τ){ e } def y(x){ e’ } • y[τ1,...,τn](e) y(e’) Function definitions Function calls Program is sequence of function definitions
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 No rule for if-expressions yet
def id (x) { x } def id[X] (x:X) { x } : ∀X. X → X def id[] (x:Int) { x } : Int → Int def id[Y,Z] (x:Y*Z) { x } : ∀Y,Z. Y*Z → Y*Z Infinitely many valid types for id...
∀X. X → X • Int → Int • ∀Y,Z. Y*Z → Y*Z ... but ∀X. X → X is the principal type
∀X. X → X • Int → Int • ∀Y,Z. Y*Z → Y*Z • ∀X. X*X → X*X • Int*Bool → Int*Bool • Int*Int → Int*Int ... but ∀X. X → X is the principal type More general than every other type Allows id to be used in most different ways
∀A. {a:A} → A • {a:Int} → Int • ∀A,B. {a:A;b:B} → A • ∀B. {a:Int;b:B} → Int def readA (x) { x.a } def readA[A] (x:{a:A}) { x.a } : ∀A. {a:A} → A This is the best type
∀A.{a:A}→{a:A} • ∀A,B.{a:A;b:B}→{a:A;b:B} • allows • foo({a=1}) • allows • foo({a=1;b=2}).b def foo (o) { let _ = o.a in o } 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
Annotated Types τ ::= | Int | Bool | ... | {fi:τi} | X
Annotated Types • “this type variable is for parameter of y and then projected on sequence of fields l” τ ::= | Int | Bool | ... | {fi:τi} | Xy.l
Annotated Types • def id (x) { x } • ∀X. X → X • ∀Xid. Xid→ Xid τ ::= | Int | Bool | ... | {fi:τi} | Xy.l
Annotated Types • def readA (x) { x.a } ∀A. {a:A} → A • ∀XreadA.a. {a:XreadA.a} → XreadA.a τ ::= | Int | Bool | ... | {fi:τi} | Xy.l
Annotated Types • def readAB (x) { x.a.b } ∀AB. {a:{b:AB}} → AB • ∀XreadA.a.b. {a:{b:XreadA.a.b}} → XreadA.a.b τ ::= | Int | Bool | ... | {fi:τi} | Xy.l
Annotated Types τ ::= | Int | Bool | ... | {fi:τi} | Xy.l
Annotated Types • “this record type came from parameter of y andthen projected on sequence of fields l” τ ::= | Int | Bool | ... | {fi:τi} | {fi:τi}y.l | Xy.l
Annotated Types τ ::= | Int | Bool | ... | {fi:τi} | {fi:τi}y.l | Xy.l • def foo (o) { let _ = o.a in o } ∀A. {a:A} → {a:A} ∀Xfoo.a. {a:Xfoo.a} → {a:Xfoo.a}foo
Annotated Types τ ::= | Int | Bool | ... | {fi:τi} | {fi:τi}y.l | Xy.l • def foo (o) { let _ = o.a in o } ∀A,B. {a:A;b:B} → {a:A;b:B} ∀Xfoo.a,Xfoo.b. {a:Xfoo.a;b:Xfoo.b} → {a:Xfoo.a;b:Xfoo.b}foo
Iterative Inference – Example 1 def foo (o) { let _ = o.a in o } def main () { foo({a=1}) } foo : ∀Xfoo.a. {a:Xfoo.a} → {a:Xfoo.a}foo Processing foo... • Xfoo <: {a : Xfoo.a} Iteration 0
Iterative Inference – Example 1 def foo (o) { let _ = o.a in o } def main () { foo({a=1}) } foo : ∀Xfoo.a. {a:Xfoo.a} → {a:Xfoo.a}foo Processing main... • {a=1} : {a:Int} ✓ • {a:Int} ≤ {a:Xfoo.a} ? Iteration 0
Iterative Inference – Example 2 def foo (o) { let _ = o.a in o } def main () { let z = {a=1;b=2} in foo(z).b } foo : ∀Xfoo.a. {a:Xfoo.a} → {a:Xfoo.a}foo Processing foo... • Xfoo <: {a : Xfoo.a} Iteration 0
Iterative Inference – Example 2 def foo (o) { let _ = o.a in o } def main () { let z = {a=1;b=2} in foo(z).b } foo : ∀Xfoo.a. {a:Xfoo.a} → {a:Xfoo.a}foo Processing main... • z : {a:Int;b:Int} ✓ • {a:Int;b:Int} ≤ {a:Xfoo.a} ? Iteration 0
Iterative Inference – Example 2 def foo (o) { let _ = o.a in o } def main () { let z = {a=1;b=2} in foo(z).b } Caller-induced constraints • Xfoo <: {b : Xfoo.b} foo : ∀Xfoo.a. {a:Xfoo.a} → {a:Xfoo.a}foo Processing main... • z : {a:Int;b:Int} • {a:Int;b:Int} ≤ {a:Xfoo.a} ? • foo(z) : {a:Int}foo ✗* ✗ • {a:Int}foo ≤ {b:X} ? Iteration 0
Iterative Inference – Example 2 def foo (o) { let _ = o.a in o } def main () { let z = {a=1;b=2} in foo(z).b } Caller-induced constraints • Xfoo <: {b : Xfoo.b} foo : ∀Xfoo.a. {a:Xfoo.a;b:Xfoo.b} → {a:Xfoo.a;b:Xfoo.b}foo Processing foo... • Xfoo <: {a : Xfoo.a} Iteration 1
Iterative Inference – Example 2 def foo (o) { let _ = o.a in o } def main () { let z = {a=1;b=2} in foo(z).b } Caller-induced constraints • Xfoo <: {b : Xfoo.b} foo : ∀Xfoo.a. {a:Xfoo.a;b:Xfoo.b} → {a:Xfoo.a;b:Xfoo.b}foo Processing main... • z : {a:Int;b:Int} ✓ • {a:Int;b:Int} ≤ {a:Xfoo.a;b:Xfoo.b} ? Iteration 1
Iterative Inference – Example 2 def foo (o) { let _ = o.a in o } def main () { let z = {a=1;b=2} in foo(z).b } Caller-induced constraints • Xfoo <: {b : Xfoo.b} foo : ∀Xfoo.a. {a:Xfoo.a;b:Xfoo.b} → {a:Xfoo.a;b:Xfoo.b}foo Processing main... • z : {a:Int;b:Int} • {a:Int;b:Int} ≤ {a:Xfoo.a;b:Xfoo.b} ? • foo(z) : {a:Int;b:Int}foo ✓ • {a:Int;b:Int}foo ≤ {b:X} ? Iteration 1
Iterative Inference – Example 3 ✓ def foo (o) { let _ = o.a in o } def main () { let _ = foo({a=1}) in let z = {a=1;b=2} in foo(z).b } ✓ foo : ∀Xfoo.a. {a:Xfoo.a} → {a:Xfoo.a}foo Iteration 0
Iterative Inference – Example 3 def foo (o) { let _ = o.a in o } def main () { let _ = foo({a=1}) in let z = {a=1;b=2} in foo(z).b } ✗* foo : ∀Xfoo.a. {a:Xfoo.a} → {a:Xfoo.a}foo Iteration 0
Iterative Inference – Example 3 ✗ def foo (o) { let _ = o.a in o } def main () { let _ = foo({a=1}) in let z = {a=1;b=2} in foo(z).b } foo : ∀Xfoo.a. {a:Xfoo.a;b:Xfoo.b} → {a:Xfoo.a;b:Xfoo.b}foo • Calling contexts impose incompatibleconstraints on type of foo Iteration 1
E− Static Type Inference In iteration 0, don’t have calling context info Iteratively constraint Xfoo as needed Process foo and its callers again No principal types, but still static inference Can run-time information improve algorithm?
E− Type Inference with Run-time Logs • Rig evaluation to log caller-induced constraints • Wrap all values with sets of type variables • When a value passed to function y, add Xy tag • When a value with tag Xy.lprojected on field f, record Xy.l <: {f : Xy.l.f} • Iteration 0 of inference looks in log forcaller-induced constraints
def foo (o) { let _ = o.a in o } def main () { let z = {a=1;b=2} in foo(z).b } main() ⇒ let z = {a=1;b=2} in foo(z).b ⇒ let z = [{a=1;b=2},{}] in foo(z).b ⇒ foo([{a=1;b=2},{Xfoo}]).b ⇒ (let _ = [{a=1;b=2},{Xfoo}].a in [{a=1;b=2},{Xfoo}]).b ⇒ (let _ = [1,{Xfoo.a}] in [{a=1;b=2},{Xfoo}]).b ⇒ [{a=1;b=2},{Xfoo}].b ⇒ [2,{Xfoo.b}] Run-time log • Xfoo <: {a : Xfoo.a} • Caller-inducedconstraint • Xfoo <: {b : Xfoo.b} Evaluation
System E− Summary • Fully static inference needs to iterate • Can wrap run-time values with sets of type variables and record field read constraints • If all expressions executed, then • log contains all caller-induced constraints • no need for iteration
Next Stop System E≤ System E System E bounded quantification subtyping System E− + polymorphism
E Type System • if b then 1 else 2 • : Int ✗ • if b then 1 else true • if b then {f=1; g=“”} • else {f=2; h=true} • : {f:Int} • if b then {f=“”} • else {f=true} ✗ Type of if-expression is join of branch types
shorthand for def bar (o) { if1 o.1.n > 0 then o.1 else o.2 } • τ1*τ2 shorthand for {1:τ1;2:τ2} def bar (x,y) { if1 x.n > 0 then x else y }