phantom types and subtyping
DESCRIPTION
Phantom Types and Subtyping. Matthew Fluet Riccardo Pucella Dept. of Computer Science Cornell University. The Setting (I). Modern strongly typed functional language Parametric polymorphism val id: = fn x => x Type constraints val idInt: int int = fn x => x - PowerPoint PPT PresentationTRANSCRIPT
Phantom Types and Subtyping
Matthew Fluet Riccardo Pucella
Dept. of Computer ScienceCornell University
TCS2002 2
The Setting (I)
• Modern strongly typed functional language– Parametric polymorphism
• val id: = fn x => x
– Type constraints• val idInt: int int = fn x => x
– Datatypes and type constructors• datatype tree = Leaf of | Node of tree tree
TCS2002 3
The Setting (II)
• Modern strongly typed functional language– Limited expressibility at foreign function
interfaces• No polymorphic types• No user defined datatypes
– No primitive notion of subtyping
TCS2002 4
The Problem
datatype atom = I of Int | B of boolfun mkI (i:int):atom = I(i)fun mkB (b:bool):atom = B(b)fun toString (v:atom):string = ...fun double (v:atom):atom = ...fun conj (v1:atom, v2:atom):atom = ...
toString (mkI 1) “1”
toString (mkB false) “false”
double (mkB true) run-time error
conj (mkI 3, mkB true) run-time error
TCS2002 5
Wish List
• Raise compile-time type errors on domain violations rather than run-time errors– toString should apply to all atoms– double should only apply to integer atoms– conj should only apply to boolean atoms
• Preserve the implementation• Would like to treat integer and boolean
atoms as subtypes of all atoms
TCS2002 6
A First Solution (I)
type All, Int, Booldatatype atom = I of int | B of boolfun mkI (i:int):Int atom = ...fun mkB (b:bool):Bool atom = ...fun toString (v:All atom):string = ...fun double (v:Int atom):Int atom = ...fun conj (v1:Bool atom, v2:Bool atom):Bool atom = ...
double (mkB true) compile-time type error; Int atom Bool atom
conj (mkI 3, mkB true) compile-time type error; Bool atom Int atomtoString (mkI 1) compile-time type error; Int atom All atom
toString (mkB false) compile-time type error; Bool atom All atom
TCS2002 7
Phantom Types
type All, Int, Booldatatype atom = I of int | B of boolfun mkI (i:int):Int atom = ...fun mkB (b:bool):Bool atom = ...
• Phantom types– Abstract types that need not have any
corresponding run-time values
• Phantom type variables– Type instantiations of in atom do not
contribute to the run-time representation of atoms
TCS2002 8
A First Solution (II)
type All, Int, Booldatatype atom = I of int | B of boolfun mkI (i:int):Int atom = ...fun mkB (b:bool):Bool atom = ...fun toString (v:All atom):string = ...fun double (v:Int atom):Int atom = ...fun conj (v1:Bool atom, v2:Bool atom):Bool atom = ...fun intToAll (v:Int atom):All atom = vfun boolToAll (v:Bool atom):All atom = v
toString (intToAll (mkI 1)) “1”
toString (boolToAll (mkB false)) “false”
TCS2002 9
A Better Solution
type All, Int, Booldatatype atom = I of int | B of boolfun mkI (i:int):Int atom = ...fun mkB (b:bool):Bool atom = ...fun toString (v: atom):string = ...fun double (v:Int atom):Int atom = ...fun conj (v1:Bool atom, v2:Bool atom):Bool atom = ...
double (mkB true) compile-time type error; Int atom Bool atom
conj (mkI 3, mkB true) compile-time type error; Bool atom Int atomtoString (mkI 1) well typed; Int atom unifies with atom
toString (mkB false) well typed; Bool atom unifies with atom
TCS2002 10
The Phantom Types Technique
• Use a superfluous type variable and type constraints to encode “extra” information
• Underlies many interesting uses of type systems– Foreign function interfaces– Embedded languages– Uncaught exception analysis
• A “folklore” technique
TCS2002 11
Contributions
• A general encoding of subtyping hierarchies into phantom types
• A formalization of one use of the phantom types technique
TCS2002 12
Outline
• A recipe for interfaces and implementations
• Encoding subtyping hierarchies• Bounded polymorphism• Formalization
TCS2002 13
From Subtyping to Polymorphism
• Features of the example– An underlying primitive type of values– A set of operations– A hierarchy of implicit subtypes
• mkI 1– Int– int atom
• toString– All string atom string
All
Int Bool
TCS2002 14
The Recipe
• Given:– A primitive type p – An implicit subtyping hierarchy 1,…,n – An implementation of p and its operations
• Derive– A “safe” interface (the types)– A “safe” implementation (the code)
• Restrictions– Shared representation and operations
TCS2002 15
Applying the Recipe
• Given:– A primitive type: atom – An implicit subtyping hierarchy: All, Int,
Bool – An implementation: structure Atom
• Derive– A “safe” interface: signature SAFE_ATOM– A “safe” implementation: structure
SafeAtom
TCS2002 16
Deriving the Interface (I)
• Introduce type • Encode each implicit type as
– 1 unifies with 2 iff 1 2
– 1C unifies with 2A iff 1 2
• Example:AllC = unit AllA = IntC = int IntA = int
BoolC = bool BoolA = bool
TCS2002 17
Deriving the Interface (II)
• Use concrete encodings in all covariant type positions
• Use abstract encodings in most contravariant type positions
TCS2002 18
Deriving the Interface (III)
signature ATOM = sig type atom val mkI: int -> atom val mkB: bool -> atom val toString: atom -> string val double: atom -> atom val conj: atom * atom -> atomend
signature SAFE_ATOM = sig type atom val mkI: int -> IntC atom val mkB: bool -> BoolC atom val toString: AllA atom -> string val double: IntA atom -> IntC atom val conj: BoolA atom * BoolA atom -> BoolC atomend
TCS2002 19
Applying the Recipe
• Given:– An abstract type: atom p – An implicit subtyping hierarchy: All, Int,
Bool p – An implementation: structure Atom p
• Derive– A “safe” interface: signature SAFE_ATOM– A “safe” implementation: structure
SafeAtom
TCS2002 20
Deriving the Implementation (I)
• Need a type isomorphic to p
– the type system should consider 1 and 2 equivalent iff 1 and 2 are equivalent
• Opaque signature constraint– Hides all type implementation details
TCS2002 21
Deriving the Implementation (II)
structure SafeAtom1:> SAFE_ATOM = struct type atom = Atom.atom val mkI = Atom.mkI val mkB = Atom.mkB val toString = Atom.toString val double = Atom.double val conj = Atom.conjend
TCS2002 22
Applying the Recipe
• Given:– An abstract type: atom p – An implicit subtyping hierarchy: All, Int,
Bool p – An implementation: structure Atom p
• Derive– A “safe” interface: signature SAFE_ATOM– A “safe” implementation: structure
SafeAtom
TCS2002 23
Encoding Subtyping Hierarchies (I)
• Powerset lattice encoding– S = {s1,…,sn} is a finite set– Ordered by inclusion
X SXC = t1 … tn where ti = unit if si X
unit z otherwiseXA = t1 … tn where ti = i if si X
i z otherwise
TCS2002 24
Encoding Subtyping Hierarchies (II)
AllC = unit unit
IntC = unit unit z
BoolC = unit z unit
NoneC = unit z unit z
AllA = 1 2
IntA = 1 2 zBoolA = 1 z 2
NoneA = 1 z 2 z
All = {s1, s2}
Int = {s1} Bool = {s2}
None = {}
TCS2002 25
Encoding Subtyping Hierarchies (III)
• Any finite hierarchy can be embedded in the powerset lattice of a set S
• Better encodings for specific classes of hierarchies
TCS2002 26
Bounded Polymorphism
• Extends both parametric polymorphism and subtyping– double: Int. – toString: All. string– plus: Int.( )
• Provides a connection between type instantiation and subtyping
• We can safely encode a restricted form of bounded polymorphism using a simple extension of our recipe
TCS2002 27
Formalization
• Translation – From a language with a restricted
form of bounded polymorphism– To a language with parametric
polymorphism– Using the “recipe” given earlier– See paper for details
TCS2002 28
Conclusion
• Use type equivalence to encode information in a free type variable
• Use unification to enforce a particular relation on the information
• Practical issues– complexity of types
TCS2002 29
TCS2002 30
The Problem (II)
datatype atom = I of int | B of boolfun mkI (i:int):atom = I(i)fun mkB (b:bool):atom = B(b)fun toString (v:atom):string = case v of I(i) => Int.toString(i) | B(b) => Bool.toString(b)fun double (v:atom):atom = case v of I(i) => I (2 * i) | _ => raise (Fail “type mismatch”)fun conj (v1:atom, v2:atom):atom = case (v1,v2) of (B(b1),B(b2)) => B (b1 andalso b2) | _ => raise (Fail “type mismatch”)
TCS2002 31
A Better Solution (II)
type All = Int = Bool = unitdatatype atom = I of int | B of boolfun mkI (i:int):Int atom = I(i)fun mkB (b:bool):Bool atom = B(b)fun toString (v: atom):string = case v of I(i) => Int.toString(i) | B(b) => Bool.toString(b)fun double (v:Int atom):Int atom = case v of I(i) => I (2 * i) | _ => raise (Fail “type mismatch”)fun conj (v1:Bool atom, v2:Bool atom):Bool atom = case (v1,v2) of (B(b1),B(b2)) => B (b1 andalso b2) | _ => raise (Fail “type mismatch”)
TCS2002 32
Bounded Polymorphism (I)
• Extends both parametric polymorphism and subtyping . .()
TCS2002 33
• IntA IntC
Bounded Polymorphism (II)
• Example: Nat Int– double: Int.
• IntA IntA • where = IntA
– plus: Int.( )• where = IntA • plus (mkI 1, natToInt (mkN 2))
TCS2002 34
Bounded polymorphism (III)
• Limitations– Type variable bounds
• ..() • where = A and = A
– First-class polymorphism– Functional subtyping
• (1 2). 2
• 2C where = 1C 2A
TCS2002 35
Formalization (II)
let f1 = 11 . x:1. c1 x in
…let fn = nn . x:n. cn x in
[]
“safe” interface types