520 likes | 638 Vues
This talk explores the interface of dynamic languages and type systems, focusing on the challenges posed by the lack of static types that complicate rapid prototyping and maintenance. We discuss System D, a novel type system that employs nested refinements, allowing for the inclusion of first-class functions inside objects while ensuring enhanced expressiveness. Our approach leverages quantifier-free formulas to boost automation and usability. We present instances illustrating how to utilize dynamic language idioms while maintaining reliability through automatic type checking and decidable refinement logic.
E N D
Nested Refinements: A Logic for Duck Typing :: Ravi Chugh, Pat Rondon, Ranjit Jhala (UCSD)
What are “Dynamic Languages”? tag tests affect control flow dictionary objects indexed by arbitrary string keys first-class functions can appear inside objects 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)
tag tests affect control flow dictionary objects indexed by arbitrary string keys first-class functions can appear inside objects • Problem: Lack of static types • … makes rapid prototyping / multi-language applications easy • … makes reliability / performance / maintenance hard • This Talk: System D • … a type system for these features
tag tests affect control flow dictionary objects indexed by arbitrary string keys first-class functions can appear inside objects Usability Our Approach: Quantifier-free formulas F≤ 1. increase expressiveness 2. retain level of automation ∨, ∧ refinement types nested refinements occurrence types … Coq syntactic approaches dependent approaches Expressiveness
tag tests affect control flow dictionary objects indexed by arbitrary string keys x :: {|tag()=“Int”tag()=“Bool”} {|tag()=“Dict” tag(sel(,“n”))=“Int” tag(sel(,m))=“Int”} d :: first-class functions can appear inside objects Challenge: Functions inside dictionaries
Key Idea: Nested Refinements 1 + d[f](0) • {|tag()=“Dict” • sel(,f):: • } d :: {|tag()=“Int”} {|tag()=“Int”} uninterpreted predicate“x::U” says“x has-type U” syntactic arrow type…
Key Idea: Nested Refinements 1 + d[f](0) • {|tag()=“Dict” • sel(,f):: • } d :: {|tag()=“Int”} {|tag()=“Int”} uninterpreted predicate“x::U” says“x has-type U” syntactic arrow type… … but uninterpreted constant in the logic
Key Idea: Nested Refinements • All values described by refinement formulas T ::= {|p} • “Has-type” predicate for arrows in formulas p ::= … | x::y:T1T2 • Can express idioms of dynamic languages • Automatic type checking • Decidable refinement logic • Subtyping = SMT Validity + Syntactic Subtyping
Outline Intro Examples Subtyping Type Soundness Conclusion
x:{|tag()=“Int”tag()=“Bool”} {|tag()=tag(x)} x:IntOrBool{|tag()=tag(x)} let negate x = if tagof x =“Int” then 0 – x else not x tagof :: y:Top{|=tag(y)} y:{|true}
✓ x:IntOrBool{|tag()=tag(x)} let negate x = if tagof x =“Int” then 0 – x else not x type environment Γ tag(x)=“Int” x :: 0 - x:: x :: IntOrBool {|Int()} {|Int()} ✓ SMT Solver ✓
✓ x:IntOrBool{|tag()=tag(x)} let negate x = if tagof x =“Int” then 0 – x else not x type environment Γ not(tag(x)=“Int”) x :: not x:: x :: IntOrBool {|Bool()} {|Bool()} ✓ SMT Solver ✓
Nesting structure hidden with syntactic sugar x:IntOrBool{|tag()=tag(x)} x: IntOrBool {|tag()=tag(x)} {|:: }
Dictionary Operations Types in terms of McCarthy operators mem :: get :: set :: d:Dictk:Str{| =true has(d,k)} d:Dictk:{|has(d,)}{| =sel(d,k)} d:Dictk:Strx:Top{| =upd(d,k,x)}
d:Dictc:StrInt get d c let getCount d c = if mem d c then toInt (d[c])else 0 safe dictionary key lookup {| =true has(d,c)}
d:Dictc:StrInt let getCount d c = if mem d c then toInt (d[c])else 0 tag(sel(,c))=“Int” d:Dictc:Str{|EqMod(,d,c) Int([c])} let incCount d c = let i = getCountd c in{d with c = i + 1} set d c (i+1)
Adding Type Constructors T ::= {|p} p ::= … | x::U U ::= y:T1T2 | A | ListT | Null “type terms”
let apply f x = fx ∀A,B.{|::{|::A}{|::B}} {|::A} {|::B} ∀A,B.(AB)AB
let dispatch d f = d[f](d) ∀A,B.d:{|::A}f:{|d[]::AB}{|::B} a form of“bounded quantification” d::A but additional constraints on A ≈ ∀A<:{f:AB}.d::A
∀A,B. {|::AB}{|::List[A]}{|::List[B]} ∀A,B.(AB)List[A]List[B] letmapfxs = if xs=null then null else new List(fxs[“hd”], map f xs[“tl”]) encode recursive data as dictionaries
let filterfxs = if xs=null then null else if not (f xs[“hd”]) then filterf xs[“tl”] else new List(xs[“hd”], filterf xs[“tl”]) ∀A,B.(x:A{|n=truex::B} List[A]List[B] usual definition,but an interesting type
Outline Intro Examples Subtyping Type Soundness Conclusion
type environment Γ _ | SMT Γ=42tag()=“Int” applyInt (42,negate) applyInt :: negate :: x:IntOrBool{|tag()=tag(x)} (Int,IntInt)Int ✓ Γ {|=42}<Int
type environment Γ …negate::x:IorB{|tag()=tag(x)} …=negate ::IntInt SMT _ | applyInt (42,negate) applyInt :: negate :: x:IntOrBool{|tag()=tag(x)} (Int,IntInt)Int Γ {|=negate}<{|::IntInt}
type environment Γ …negate::x:IorB{|tag()=tag(x)} …=negate ::IntInt SMT _ | applyInt (42,negate) applyInt :: negate :: x:IntOrBool{|tag()=tag(x)} (Int,IntInt)Int distinct uninterpreted constants! ✗ Γ {|=negate}<{|::IntInt}
Invalid, since these are uninterpreted constants ::x:IorB{|tag()=tag(x)} ::IntInt tag()=“Int” tag()=“Int” tag()=“Bool” tag()=“Int”tag()=tag(x) tag()=“Int” ✗ Want conventional syntactic subtyping Int <: IorB {|tag()=tag(x)} <: Int ✓ ✓ IorB {|tag()=tag(x)} <: Int Int
Subtyping with Nesting To prove pq: 1) Convert qto CNF clauses (q11…)…(qn1…) 2) For each clause, discharge some literal qij as follows: base predicate:pqij anything except x::U e.g. tag()=tag(x) e.g. tag(sel(d,k))=“Int”
Subtyping with Nesting To prove pq: 1) Convert qto CNF clauses (q11…)…(qn1…) 2) For each clause, discharge some literal qij as follows: base predicate:pqij “has-type” predicate:px::U Subtyping Subtyping Arrow Rule Implication Implication U’<:U px::U’ pqij SMT Solver SMT Solver
applyInt (42,negate) …negate::x:IorB{|tag()=tag(x)} …=negate ::x:IorB{|tag()=tag(x)} UninterpretedReasoning _ | + SyntacticReasoning Γ x:IorB{|tag()=tag(x)}<:IntInt _ Γ {|=negate}<{|::IntInt} |
Outline Intro Examples Subtyping Type Soundness Conclusion
x:Tx,Γ e[::T v :: Tx and If SubstitutionLemma Γ[v/x] e[v/x]::T[v/x] then _ _ _ | | | independent of 0, and just echoes the binding from the environment x.x+1 ::{|::IntInt} _ _ _ f:{|::IntInt} 0::{|f::IntInt} | | | 0::{|x.x+1::IntInt}
x:Tx,Γ e[::T v :: Tx and If SubstitutionLemma Γ[v/x] e[v/x]::T[v/x] then _ _ _ | | | SMT =0x.x+1::IntInt 1st attempt ✗ _ 0::{|=0} {|=0}<{|x.x+1::IntInt} | 0::{|x.x+1::IntInt}
x:Tx,Γ e[::T v :: Tx and If SubstitutionLemma ✗ Γ[v/x] e[v/x]::T[v/x] then _ _ _ | | | ✗ SMT =0::U’ + 2nd attempt Arrow U’ <:IntInt _ 0::{|=0} {|=0}<{|x.x+1::IntInt} | 0::{|x.x+1::IntInt}
[S-Valid-Uninterpreted] • Rule not closed under substitution • Interpret formulas by “hooking back” into type system • Stratification to create ordering for induction I I iff = = | | [S-Valid-Interpreted] SMT Γpq x.x+1::{|::IntInt} Γpq n _ | n Γ {|=p}<{|=q} Γ {|=p}<{|=q} _ _ x.x+1::IntInt | | n n-1
Type Soundness StratifiedSubstitutionLemma x:Tx,Γ e[::T If n _ _ _ _ v :: Tx and | | | | n Γ[v/x] e[v/x]::T[v/x] then n+1 • “Level 0” for type checking source programs, • using only [S-Valid-Uninterpreted] Stratified Preservation e v e[::T and If 0 _ v[::T for some m then | m • artifact of the metatheory
Recap • Dynamic languages make heavy use of: • run-time tag tests, dictionary objects, lambdas • Nested refinements • generalizes refinement type architecture • enables combination of dictionaries and lambdas • Decidable refinement logic • all proof obligations discharged algorithmically • novel subtyping decomposition to retain precision • Syntactic type soundness
Future Work • Imperative Updates • Inheritance (prototypes in JS, classes in Python) • Applications • More local type inference / syntactic sugar • Dictionaries in statically-typed languages
Thanks! D :: ravichugh.com/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)
Onto functional version of Dojo function 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:{|=null Str()::ATop} obj:{|::A (f=null::AInt) (Str(f)[f]::AInt)} List[TopTop] onto ::
Onto (2) functional version of Dojo function 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|g=null 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 ::
Approach: Refinement Types • Reuse refinement type architecture • Find a decidable refinement logic for • Tag-tests • Dictionaries • Lambdas • Define nested refinement type architecture ✓ ✓ ✗ ✓*
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
Subtyping (Traditional Refinements) T ::= {|p} | x:T1T2 Subtyping traditional refinements tag()=“Int”true Implication Int <: Top SMT Solver
Subtyping (Traditional Refinements) T ::= {|p} | x:T1T2 Subtyping traditional refinements tag()=“Int” true tag()=“Int” tag()=“Int” Implication SMT Solver Int <: Top Int <: Int Top Int <: Int Int