singleton kinds and singleton types christopher a. stone august 2, 1999 thesis committee bob harper,...
TRANSCRIPT
Singleton Kinds and Singleton Types
Christopher A. StoneAugust 2, 1999
Thesis CommitteeBob Harper, chair
Peter LeeJohn Reynolds
Jon Riecke (Bell Laboratories)
Dissertation Summary
• Study of language with singleton kinds and singleton types– Models intermediate representation of TILT
compiler
• Proofs of important language properties– Decidability of typechecking– Algorithms for typechecking
• Context-sensitive type-equivalence algorithm
– Soundness of type system
• Here I will concentrate on singleton kinds.
TILT Compiler for Standard ML
• Reimplementation of the TIL prototype [Tarditi et. al. 96]
– Compiles full Standard ML– Handles separate compilation, larger
programs
• Key technologies– Intensional Polymorphism [Harper & Morrisett 95]
– Typed Compilation [Morrisett 95]
Typed Compilation
• Uses strongly-typed intermediate language(s)
• Compiler passes preserve well-typedness
prog1 : t
prog2 : t
Common Subexpression Elimination
prog1 : t1
prog2 : t2
Closure Conversion
Advantages of Typed Compilation
• Retains information– For optimizations– For improved code generation– For safety certificates
• Also a handy compiler debugging tool!– Typechecking after each transformation
finds many common implementation errors.– Improves compiler robustness
One Pass: Phase Splitting
• SML module system– Structures package types, values, and structures– Functors map modules to modules
• Phase Splitting transformation [Harper et al. 90]
– Purpose: Translate from language with modules to a language without
– Every module splits into a type part and a value part:
– Interfaces split in a parallel fashion
An Interface for Queuesstructure Queue: sig type elem type queue val empty : queue val enqueue : elem * queue -> queue val dequeue : queue -> elem * queueend
Phase Splitting Queuesstructure Queue: sig type elem type queue val empty : queue val enqueue : elem * queue -> queue val dequeue : queue -> elem * queueend
Queue_c : {elem : TYPE, queue : TYPE}
Queue_r : {empty : …, enqueue : …, dequeue : …}
Queues of Stringsstructure StringQueue: sig type elem = string type queue val empty : queue val enqueue : elem * queue -> queue val dequeue : queue -> elem * queueend
Queues of Stringsstructure StringQueue: sig type elem = string type queue val empty : queue val enqueue : elem * queue -> queue val dequeue : queue -> elem * queueend
Makes elem a synonym for string
Queues of Stringsstructure StringQueue: sig type elem = string type queue val empty : queue val enqueue : elem * queue -> queue val dequeue : queue -> elem * queueend
StringQueue_c : {elem : ????, queue : TYPE}
StringQueue_r : {empty : …, enqueue : …, dequeue : …}
A First Trystructure StringQueue: sig type elem = string type queue val empty : queue val enqueue : elem * queue -> queue val dequeue : queue -> elem * queueend
StringQueue_c : {elem : TYPE, queue : TYPE}
StringQueue_r : {empty : …, enqueue : …, dequeue : …}
How to remember elem string?
• Option 1: Substitution– Replace elem with string everywhere [Shao 99]
• Must remove indirect references as well
– But in general this causes duplication of types• Instead of string, might have a big, complex type• Replacing elem with this type everywhere could cause
blow-up in code size• In TILT types can correspond to run-time
computations
How to remember elem string?
• Option 1: Substitution– Replace elem with string everywhere [Shao 99]
• Must remove indirect references as well
– But in general this causes duplication of types• Instead of string, might have a big, complex type• Replacing elem with this type everywhere could cause
blow-up in code size• in TILT types can correspond to run-time computations
• Option 2: Put definitions in the kind structure
What is a kind?
• In SML, types form a little language
• Kinds are the “types of types”
– key and key pair are well-formed types– pair and pair pair are not
type key = inttype ‘a pair = ‘a * ‘a
key :: TYPEpair :: TYPE TYPE
Singleton Kinds
• S(A) is “kind of all types interchangeable with A”– i.e., B :: S(A) if and only if B A
• Advantages– Single construct isolating issues of type
definitions – Not conflated with ML module system – Syntactically simple– Necessary for type-preserving polymorphic
closure conversion [Minamide et al. 96]
Queues of Strings with Singletons
structure StringQueue: sig type elem = string type queue val empty : queue val enqueue : elem * queue -> queue val dequeue : queue -> elem * queueend
StringQueue_c : {elem :: S(string), queue :: TYPE}
StringQueue_r : {empty : …, enqueue : …, dequeue : …}
Typechecking with Singletons
• Want to be able to typecheck after phase-splitting– Initial attempts to program typechecker
failed• Rejected valid inputs• Went into infinite loops
– Question: is typechecking even decidable?
• The key to typechecking is determining equivalence of type constructors
MIL0 Syntax (excerpt)
• Type Constructors A,B ::= int | bool | ... | |:K.A | A B | <A,B> | 1A | 2A
• Kinds K,L ::= T | S(A) | :K.L (K L) | :K.L (K L)
Static Semantics
• Standard rules for dependent kinds and -equivalence of constructors, plus:– Singleton introduction and elimination
• If A : T then A : S(A)
• If A : S(B) then A B : T
– Subkinding relation: S(A) T• Lifted to and kinds as usual
– Two non-standard typing rules• Ensures types preserved under -equivalence• e.g., can show if f : T T then f : :T. S(f)
Singletons make Equivalence Interesting
• Dependency on kinds of free variables: :: TS(int) 2 int :: T
:: ::T.S() 1 2 :: T
:: TS(int) :T.int :: TT
Singletons make Equivalence Interesting
• Dependency on kinds of free variables: :: TS(int) 2 int :: T
:: ::T.S() 1 2 :: T
:: TS(int) :T.int :: TT
• Dependency on classifying kind– Cannot show ::T.int ::T. :: TT
Singletons make Equivalence Interesting
• Dependency on kinds of free variables: :: TS(int) 2 int :: T
:: ::T.S() 1 2 :: T
:: TS(int) :T.int :: TT
• Dependency on classifying kind– Cannot show ::T.int ::T. :: TT
– But, ::T.int :T. :: S(int)T holds!
Singletons make Equivalence Interesting
• Dependency on kinds of free variables: :: TS(int) 2 int :: T
:: ::T.S() 1 2 :: T
:: TS(int) :T.int :: TT
• Dependency on classifying kind– Cannot show ::T.int ::T. :: TT
– But, ::T.int :T. :: S(int)T holds!
• Interestingly, -equivalence is admissible!
Typechecking with Singletons
• Not immediately obvious that typechecking is decidable in the presence of singletons
• Standard context-free rewriting techniques not directly applicable– Adapting these techniques can be hard
• e.g., Lillibridge, Curien and Ghelli
• My method (inspired by Coquand)– Define a direct comparison algorithm– Prove it correct using logical relations
Algorithmic Equivalence
• Deterministic rules for A B :: K– Expect agreement with A B :: K
• for well-formed A, B
• Strategy: reduce to tests at kind T
Algorithmic Equivalence
• Deterministic rules for A B :: K– Expect agreement with A B :: K
• for well-formed A, B
• Strategy: reduce to tests at kind T A B :: ::K.L if 1A 1B :: K
and 2A 2B :: [1A/]L
Algorithmic Equivalence
• Deterministic rules for A B :: K– Expect agreement with A B :: K
• for well-formed A, B
• Strategy: reduce to tests at kind T A B :: ::K.L if 1A 1B :: K
and 2A 2B :: [1A/]L A B :: ::K.L if ::K A B :: L
Algorithmic Equivalence
• Deterministic rules for A B :: K– Expect agreement with A B :: K
• for well-formed A, B
• Strategy: reduce to tests at kind T A B :: :K.L if 1A 1B :: K
and 2A 2B :: [1A/]L A B :: :K.L if ::K A B :: L
A B :: S(C) always
• At kind T head-normalize, compare subcomponents– Includes expansion of definitions
Correctness Proof
• Hardest part is showing completeness of the algorithm w.r.t. provable equivalence
• Approach: logical relations– Define “logical” equivalence– Show logical equivalence implies
algorithmic equivalence ()– Show provable equivalence () implies
logical equivalence.
Logical Relations
• Provable equivalence depends on context and classifying kind
• Also, relating open terms• Suggests Kripke logical relations (relations
indexed by a “world” and a kind)– In our case, a world is a typing context– Ordering on worlds is prefix ordering
A Natural Attempt
• Relatively standard Kripke logical relations:– A is B in T [] iff A B :: T
– A is B in ::K.L [] iff ’ if A’ is B’ in K [’] then A A’ is B B’ in [A’/]L [’]
• This proof nearly goes through
The Problem
• We must show logical equivalence is symmetric and transitive
• This requires showing the algorithm symmetric and transitive
• Arbitrary choices in presentation of the algorithm prevent a direct proof, e.g.,
A B :: ::K.L if 1A 1B :: K and 2A 2B :: [1A/]L
A Revised Algorithm
• Idea: maintain a context and a classifier for each constructor– 6 place relation 1 A1 :: K1 2 A2 :: K2
– Uses both alternatives at choice points• one on each side
– Maintains invariant that 1 2 and K1 K2
• Correctness of revised algorithm implies correctness of original algorithm
Revised Logical Relations
• Because algorithm has two classifiers and two contexts, logical relations must as well:– A1 in K1 [1] is A2 in K2 [2]
• Definition retains same flavor as before– e.g., two constructors are related at (related)
-kinds at two worlds if for each pair of future worlds …
• Does not seem possible to formulate as a standard Kripke relation indexed by pairs (1,2) and (A1,A2)
Summary of Decidability
• Soundness provable directly: if A B :: K then A B :: K
• Proof just outlined shows: if A B :: K then A B :: K
• Easy corollary: Algorithm terminates on all inputs
• Thus equivalence is decidable.• Thus validity of type constructors and their
kinds is decidable [Stone and Harper 00]
Singleton Types
• S(v : ) is the type of terms of type equivalent to v– Restriction: only values may appear in singletons– Strong equivalence (no )
• Equivalence only depends on typing context, not classifier
• Similar proof strategy shows typechecking decidable
• Potential use: TILT cross-module inlining– Interfaces can expose code of module
components
Type Soundness
• Want to show “well-typed programs don’t go wrong”
• Mostly standard proof outline– Evaluation preserves well-typedness– Well-typed programs don’t get stuck”
• But, proof requires consistency property– e.g., int and bool are not provably equivalent– Follows directly by correctness of equivalence
algorithm
Adding Intensional Polymorphism
• Constructs for runtime case analysis (and primitive recursion) of type constructors – Permits improved calling conventions, data
representation even when types statically unknown [Harper and Morrisett 95]
• All the proofs go through with relatively minor modifications.– Relatively robust proof technique
Summary of Contributions
• Thorough study of a language MIL0 with singleton kinds and singleton types– Two very different forms of equivalence
• Results for type soundness and decidability• Typechecking algorithms
– TILT compiler uses those for singleton kinds– General framework for context-sensitive
equivalences
Open Questions
• Improved theory of singleton types– Currently appears to require too many type
annotations to be practical– S(v) instead of S(v : t) ?
• Nontrivial equivalences between recursive types– Extension in TILT implementation works in
practice– No obvious way to make algorithm
obviously transitive.
Related Work
• Aspinall [94, 97]– Studied -calculus with singleton types– Somewhere between my singleton types and
kinds• Has -equivalence, not , strong equivalence for
lambda abstractions, singletons contain type annotations
– Gave a PER model– Showed existence of principal types– No typechecking or equivalence algorithm
(though conjectured decidable)