440 likes | 526 Views
Nested Refinements: A Logic for Duck Typing. Ravi Chugh , Pat Rondon, Ranjit Jhala (UCSD). What are “Dynamic Languages”?. d._onto = function ( arr,obj,fn ) { if ( !fn ) { arr.push ( obj ) ; } else if ( fn ) { var func = ( typeof fn == “ string ” ) ? obj [ fn ] : fn;
E N D
Nested Refinements: A Logic for Duck Typing Ravi Chugh, Pat Rondon, Ranjit Jhala (UCSD)
What are “Dynamic Languages”? d._onto=function(arr,obj,fn) { if (!fn){ arr.push(obj); }elseif(fn){ var func =(typeof fn ==“string”) ? obj[fn] : fn; arr.push(function(){ func.call(obj); }); } } tag-tests affect control flow dictionary objects indexed by arbitrary string keys first-class functions can appear inside objects mutation inheritance affects object lookup
What are “Dynamic Languages”? • Lack of static types • … makes rapid prototyping / multi-language applications easy • … makes reliability / performance / maintenance hard • Goal: Design a type system for these features These alone are hard! tag-tests affect control flow dictionary objects indexed by arbitrary string keys first-class functions can appear inside objects mutation inheritance affects object lookup
iftagoff=“Str”thend.n + d[f](0) elsed.n + f(0) These alone are hard! tag-tests affect control flow dictionary objects indexed by arbitrary string keys first-class functions can appear inside objects
Approach: Refinement Types tag-tests Type environment tracks control flow predicates x :: {|tag()=“Int”tag()=“Bool”} x :: {|tag()=“Bool”} x :: {|tag()=“Int”} iftagofx=“Int”then 0 - xelse notx
Approach: Refinement Types tag-tests d.n + d[m] dictionaries {|tag()=“Dict” tag(sel(,“n”))=“Int” tag(sel(,m))=“Int”} d :: McCarthy axioms
Approach: Refinement Types tag-tests 1 +f(0) dictionaries first-class functions x.x :: f :: x:{|tag()=“Int”}{|tag()=“Int”} x:{|tag()=“Int”}{|n=x} Clear separation of base and arrow types T ::= {|p} | x:T1T2
Approach: Refinement Types tag-tests 1 +f(0) dictionaries first-class functions
Approach: Refinement Types tag-tests 1 + d[f](0) dictionaries d :: {|tag()=“Dict” sel(,f) ??? } first-class functions Key Challenges How to describe arrow inside formula? How to keep type checking decidable?
Approach: Refinement Types • Reuse refinement type architecture • Find a decidable refinement logic for • Tag-tests • Dictionaries • Lambdas • Define nested refinement type architecture ✓ ✓ ✗ ✓*
Nested Refinements 1 + d[f](0) • {|tag()=“Dict” • sel(,f):: • } d :: {|tag()=“Int”} {|tag()=“Int”} uninterpreted predicate“x::U” says“x has-type U” uninterpreted constantin the logic… … but syntactic arrowin the type system!
Nested Refinements • Refinement formulas over a decidable logic • uninterpreted functions, McCarthy arrays, linear arithmetic • Only base values refined by formulas All values T ::= {|p}U ::= x:T1T2p ::= pq | … | x = y | x < y | … | tag(x) = “Int” | … | sel(x,y) = z | …| x :: U T ::= {|p} | x:T1T2p ::= pq | … | x = y | x < y | … | tag(x) = “Int” | … | sel(x,y) = z | … traditional refinements
Nested Refinements • Refinement formulas over a decidable logic • uninterpreted functions, McCarthy arrays, linear arithmetic • Only base values refined by formulas • “has-type” allows “type terms” in formulas All values T ::= {|p}U ::= x:T1T2p ::= pq | … | x = y | x < y | … | tag(x) = “Int” | … | sel(x,y) = z | … | x::U T ::= {|p} | x:T1T2p ::= pq | … | x = y | x < y | … | tag(x) = “Int” | … | sel(x,y) = z | … traditional refinements
Nested Refinements • Refinement formulas over a decidable logic • uninterpreted functions, McCarthy arrays, linear arithmetic • Only base values refined by formulas • “has-type” allows “type terms” in formulas All values T ::= {|p}U ::= x:T1T2p ::= pq | … | x = y | x < y | … | tag(x) = “Int” | … | sel(x,y) = z | … | x::U
let foofd = iftagoff=“Str”thend.n + d[f](0) elsed.n + f(0) f:{|Str()::IntInt} d:{|Dict() Int(.n) Str(f)[f]::IntInt} Int foo :: T ::= {|p}U ::= x:T1T2p ::= pq | … | x = y | x < y | … | tag(x) = “Int” | … | sel(x,y) = z | … | x::U
let foofd = iftagoff=“Str”thend.n + d[f](0) elsed.n + f(0) f:{|Str()::IntInt} d:{|Dict() Int(.n) Str(f)[f]::IntInt} Int foo :: T ::= {|p}U ::= x:T1T2p ::= pq | … | x = y | x < y | … | tag(x) = “Int” | … | sel(x,y) = z | … | x::U
let foofd = iftagoff=“Str”thend.n + d[f](0) elsed.n + f(0) {|tag()=“Int”} {|tag()=“Int”} f:{|Str()::IntInt} d:{|Dict() Int(.n) Str(f)[f]::IntInt} Int foo :: tag()=“Str” T ::= {|p}U ::= x:T1T2p ::= pq | … | x = y | x < y | … | tag(x) = “Int” | … | sel(x,y) = z | … | x::U
let foofd = iftagoff=“Str”thend.n + d[f](0) elsed.n + f(0) f:{|Str()::IntInt} d:{|Dict() Int(.n) Str(f)[f]::IntInt} Int foo :: T ::= {|p}U ::= x:T1T2p ::= pq | … | x = y | x < y | … | tag(x) = “Int” | … | sel(x,y) = z | … | x::U
let foofd = iftagoff=“Str”thend.n + d[f](0) elsed.n + f(0) sel(n,“n”) sel(n,f) f:{|Str()::IntInt} d:{|Dict() Int(.n) Str(f)[f]::IntInt} Int foo :: T ::= {|p}U ::= x:T1T2p ::= pq | … | x = y | x < y | … | tag(x) = “Int” | … | sel(x,y) = z | … | x::U
let foofd = iftagoff=“Str”thend.n + d[f](0) elsed.n + f(0) f:{|Str()::IntInt} d:{|Dict() Int(.n) Str(f)[f]::IntInt} Int foo :: T ::= {|p}U ::= x:T1T2p ::= pq | … | x = y | x < y | … | tag(x) = “Int” | … | sel(x,y) = z | … | x::U
let foofd = iftagoff=“Str”thend.n + d[f](0) elsed.n + f(0) foo :: f: {|tag()=“Str”::} IntInt d: {|tag()=“Dict” tag(sel(,“n”))=“Int” tag(f)=“Str” sel(,f):: } {|:: {|:: IntInt T ::= {|p}U ::= x:T1T2p ::= pq | … | x = y | x < y | … | tag(x) = “Int” | … | sel(x,y) = z | … | x::U Int } }
Nested Refinements • Type Language • Subtyping • Extensions • Recap
Subtyping T ::= {|p} | x:T1T2 Subtyping Top Int {|true} {|tag()=“Int”} traditional refinements tag()=“Int”true Implication Int <: Top SMT Solver
Subtyping T ::= {|p} | x:T1T2 Subtyping Top Int {|true} {|tag()=“Int”} traditional refinements tag()=“Int” true tag()=“Int” tag()=“Int” Implication SMT Solver Int <: Top Int <: Int Top Int <: Int Int
Subtyping T ::= {|p} | x:T1T2 Subtyping Arrow Rule Top Int {|true} {|tag()=“Int”} traditional refinements tag()=“Int” true tag()=“Int” tag()=“Int” Implication SMT Solver Int <: Top Int <: Int Top Int <: Int Int
Subtyping T ::= {|p} | x:T1T2 Subtyping Arrow Rule traditional refinements • Decidable if: • Only values in formulas • Underlying theories decidable Implication SMT Solver • With nested refinements: • No new theories • But implication is imprecise!
Subtyping with Nesting Subtyping Implication ✗ ::TopInt::IntInt SMT Solver Invalid, as these are distinct uninterpreted constants
Subtyping with Nesting When goal is base predicate:pq When goal is “has-type” predicate:px::U Subtyping Subtyping Arrow Rule Implication Implication U’<:U px::U’ pq SMT Solver SMT Solver
Subtyping with Nesting UninterpretedReasoning Normalize formulas to subdivide obligations appropriately SyntacticReasoning + p ::TopInt TopInt<:IntInt p::Int Int
Nested Refinements • Type Language • Subtyping • Extensions • Recap
Extensions • Simple to add additional type constructors • Extend the grammar of type terms • Add additional syntactic subtyping rules U ::= x:T1T2 | A | List[T] | Null SyntacticRules Arrow Rule Covariant List Rule Null List Rule
Map ∀A,B. {|::AB}{|::List[A]}{|::List[B]} let mapfxs = ifxs=nullthen null else f xs[“hd”] :: map f xs[“tl”] encode recursive data as dictionaries
Filter let filterfxs = ifxs=nullthen null else if f xs[“hd”]then xs[“hd”] :: filter f xs[“tl”] else filter f xs[“tl”] • ∀A,B.{|::x:A{|n=Truex::B}} • {|::List[A]} • {|::List[B]} usual definition, but an interesting type
Dispatch let dispatch d f=d[f] d • ∀A,B.d:{|Dict() ::A} • {|Str() d[]::AB} • {|::B} a form of “bounded quantification” since d::A but additional constraints on A
Recap • Refinement types are a compelling approach • Dynamic dictionaries require dependency • Tag-tests require path sensitivity • But, not enough for lambdas in dictionaries • Nested refinement types are a clean solution • Natural way to describe dynamic idioms • Novel subtyping remains decidable and automatic • Interesting soundness proof
Future Work • Extend to imperative JavaScript setting • Employ strong update techniques • Track prototype chain for inheritance • Better inference • Untyped programmers allergic to annotations • Perhaps utilize run-time information • Applications • JavaScript benchmarks (e.g. SunSpider) • JavaScript frameworks (e.g. Dojo)
Thanks! D :: http://cseweb.ucsd.edu/~rchugh/research/nested/
Macros {|::} x:T1T2 x:T1T2 • Types • Formulas • Logical Values EqMod(d,d’,k) Int has(d,k) Str(x) x.k x[k] {|tag()=“Int”} tag(x) = “Str” ∀k’. k’!=k sel(d,k)!=sel(d’,k) sel(d,k)!=bot sel(n,“k”) sel(n,k)
Functional Onto let onto callbacks f obj= if f = null then new List(obj,callbacks) else let cb =if tagof f =“Str” then obj[f]else f in new List(fun()-> cb obj, callbacks) ∀A. callbacks:List[TopTop] f:{|Str()::ATop} obj:{|::A (f=null::AInt) (Str(f)[f]::AInt)} List[TopTop] onto ::
Functional Onto (2) let onto (callbacks,f,obj)= if f = null then new List(obj,callbacks) else let cb =if tagof f =“Str” then obj[f]else f in new List(fun()-> cb obj, callbacks) callbacks:List[TopTop] *f:{g|Str(g)g::{x|x=obj}Top} *obj:{o|(f=nullo::{x|x=o}Int) (Str(f)o[f]::{x|x=o}Int)} List[TopTop] onto ::
Normalization • TODO
Related Work • TODO