Abstract Datatypes: Defined by how you use one signature STACK = sig type 'a stack val empty : 'a stack val push : 'a -> 'a stack -> 'a stack val pop : 'a stack -> (‘a * 'a stack) option end 10
signature STACK = sig type 'a stack val empty : 'a stack val push : 'a -> 'a stack -> 'a stack val pop : 'a stack -> (‘a * 'a stack) option end structure ListStack : STACK = struct type 'a stack = 'a list val empty = [] fun push x s = x::s fun pop (h::t) = SOME (h,t) | pop [] = NONE end Structures 13
signature STACK = sig type 'a stack val empty : 'a stack val push : 'a -> 'a stack -> 'a stack val pop : 'a stack -> (‘a * 'a stack) option end structure ListStack : STACK = struct type 'a stack = 'a list val empty = [] fun push x s = x::s fun pop (h::t) = SOME (h,t) | pop [] = NONE end Structures 14
signature STACK = sig type 'a stack val empty : 'a stack val push : 'a -> 'a stack -> 'a stack val pop : 'a stack -> (‘a * 'a stack) option end structure ListStack : STACK = struct type 'a stack = 'a list val empty = [] fun push x s = x::s fun pop (h::t) = SOME (h,t) | pop [] = NONE end Structures 15
signature STACK = sig type 'a stack val empty : 'a stack val push : 'a -> 'a stack -> 'a stack val pop : 'a stack -> (‘a * 'a stack) option end structure ListStack : STACK = struct type 'a stack = 'a list val empty = [] fun push x s = x::s fun pop (h::t) = SOME (h,t) | pop [] = NONE end Structures 16
Signature Matching If type t implemented as , then the values specified to have type t must have type [/t].* ! *or a more general type. ! sigs-to-structs are many-to-many 18
signature STACK = sig type 'a stack val empty : 'a stack val push : 'a -> 'a stack -> 'a stack val pop : 'a stack -> ('a * 'a stack) option val size : 'a stack -> int end Extend the Signature 21
structure ListStack : STACK = struct type 'a stack = 'a list val new = [] fun push x s = x::s fun pop (x::s) = SOME (x,s) | pop [] = NONE fun size s = List.length s end Extend the Structure 22
structure SizedListStack : STACK = struct type 'a stack = ('a list * int) val empty = ([], 0) fun push x (st, sz) = (x::st, sz + 1) fun pop (x::st, sz) = SOME (x, (st, sz - 1)) | pop ([], _) = NONE fun size (st, sz) = sz end ! A More Efficient Implementation 23
structure SizedListStack : STACK = struct type 'a stack = ('a list * int) val empty = ([], 0) fun push x (st, sz) = (x::st, sz + 1) fun pop (x::st, sz) = SOME (x, (st, sz - 1)) | pop ([], _) = NONE fun size (st, sz) = sz end ! A More Efficient Implementation 24
structure SizedListStack : STACK = struct type 'a stack = ('a list * int) val empty = ([], 0) fun push x (st, sz) = (x::st, sz + 1) fun pop (x::st, sz) = SOME (x, (st, sz - 1)) | pop ([], _) = NONE fun size (st, sz) = sz end ! A More Efficient Implementation 25
From Abstraction to Composability SML: Functors!* *not to be confused with Haskell functors, Prolog functors, C++ functors, nor category-theoretic functors 29
Functor Example functor RPNCalculator (Stack : STACK) = struct datatype Oper = Num of int | Plus | Mul | … type rpn = Oper list fun compute (c : int Stack.stack) (eqn : rpn) = case eqn of [] => Stack.pop c | ((Num i)::eqn) => compute (Stack.push i) eqn | (Plus::eqn) => if (Stack.size c) < 2 then … else (* pop 2 off stack, add, push *) … end 32
Representation Independence “The behavior of clients of an ADT must be unaffected by changes to the internal representation of the ADT that are preserved by its operations.” https://wiki.mpi-sws.org/star/paramore 35
Representation Independence “The behavior of clients of an ADT must be unaffected by changes to the internal representation of the ADT that are preserved by its operations.” https://wiki.mpi-sws.org/star/paramore M ~ M’ => for all F, F(M) ~ F(M’) ! 36
Representation Independence “The behavior of clients of an ADT must be unaffected by changes to the internal representation of the ADT that are preserved by its operations.” https://wiki.mpi-sws.org/star/paramore e.g. RPNCalculator(ListStack) and RPNCalculator(SizeListStack) should behave the same way. 37
Representation Independence What’s an example of two structures matching the same signature that don’t have a relation preserved by their operations? 38
Functors Example: Tree Traversal signature WORKLIST = sig type 'a t val empty : 'a t val put : 'a t -> 'a -> 'a t val take : 'a t -> ('a * 'a t) option end 40
structure Queue : WORKLIST = struct type 'a t = 'a list val empty = [] fun put q x = q @ [x] fun take (x::q) = SOME (x,q) | take [] = NONE end Functors Example: Tree Traversal 42
datatype 'a tree = Leaf | Node of 'a tree * 'a * 'a tree ! functor Traverse (W : WORKLIST) = struct fun traverse (tr : 'a tree) : 'a list = [...] end Functors Example: Tree Traversal 43
fun traverse (tr : 'a tree) : 'a list = let fun traverse' (worklist : ('a tree) W.t) = (case S.take worklist of NONE => [] | SOME (Leaf, rest) => traverse' rest | SOME (Node(t1,v,t2), rest) => v :: (traverse' (W.put (W.put rest t1) t2))) in traverse' (W.put W.empty tr) end Functors Example: Tree Traversal 44
fun traverse (tr : 'a tree) : 'a list = let fun traverse' (worklist : ('a tree) W.t) = (case S.take worklist of NONE => [] | SOME (Leaf, rest) => traverse' rest | SOME (Node(t1,v,t2), rest) => v :: (traverse' (W.put (W.put rest t1) t2))) in traverse' (W.put W.empty tr) end Functors Example: Tree Traversal 45
fun traverse (tr : 'a tree) : 'a list = let fun traverse' (worklist : ('a tree) W.t) = (case S.take worklist of NONE => [] | SOME (Leaf, rest) => traverse' rest | SOME (Node(t1,v,t2), rest) => v :: (traverse' (W.put (W.put rest t1) t2))) in traverse' (W.put W.empty tr) end Functors Example: Tree Traversal 46
fun traverse (tr : 'a tree) : 'a list = let fun traverse' (worklist : ('a tree) W.t) = (case S.take worklist of NONE => [] | SOME (Leaf, rest) => traverse' rest | SOME (Node(t1,v,t2), rest) => v :: (traverse' (W.put (W.put rest t1) t2))) in traverse' (W.put W.empty tr) end Functors Example: Tree Traversal 47
Functors: Other Examples 51 signature ORD = sig type t val eq : t -> t -> bool val less : t -> t -> bool end signature SET = sig type elem type set val empty : set val add : elem -> set -> set […] end
Functors: Other Examples 52 functor OrdSet (Elem : ORD) : SET = structure type elem = Elem.t type set = elem list […] end signature ORD = sig type t val eq : t -> t -> bool val less : t -> t -> bool end signature SET = sig type elem type set val empty : set val add : elem -> set -> set […] end
Functors: Other Examples “A functor is a framework” ! Functioning project: give me render, update, and event handler; I’ll put the pieces together into a game. 53
Functors: Other Examples fun loop s = [...] case option_iterate Game.tick s num_ticks of NONE => () | SOME s => (Game.render screen s; case SDL.pollevent () of NONE => (SDL.delay 0; loop s) | SOME e => Option.app loop (Game.handle_event e s)) 54
Recap… We’ve covered signatures, structures; how they relate (via matching); and functors, which allow for separate modular development via representation independence. ! What kind of type theory underlies these principles? 55
Logical Content “Abstract Types Have Existential Type,” Mitchell and Plotkin 1988 stack : ∀t. ∃ s. [s ⋀ (t ⋀ s -> s) ⋀ (s -> (t ⋀ s) ⋁ ⊤)] sig type ‘a stack emp : stack push : ‘a * ‘a stack -> ‘a stack pop : ‘a -> (‘a * ‘a stack) option end c.f.: 58
Logical Content “F-ing Modules,” Rossberg, Russo, and Dreyer 2010 59 A direct account of all the essential ML module system features in terms of System F.
A stumbling block? 62 In a straightforward account of abstract types as existential quantification: No. ! In Standard ML: No. (Generative functors) In OCaml: Yes. (Applicative functors) Does S1.add 6 (S2.empty) type check?
When generativity makes sense 63 functor F () : ORD = structure type t = int val eq = Int.eq val less = if random() then Int.less else Int.greater end ! structure S1 = Set (F ()) structure S2 = Set (F ())
“Applicative/Generative = Pure/Impure, plain and simple.”! — Derek Dreyer, slides from WG2.8 Meeting, 2007 Generative vs. Applicative Functors: 64 Unfortunately, accounting for the semantics of! applicative functors is hard!! ! (see “F-ing Modules” expanded version)
Functor Types in Various MLs 65 Generative Functors Applicative Functors Higher-order Functors Known! Unsoundness? Standard ML Yes No No No OCaml Yes (Recently) Yes No No Moscow ML Yes Yes Yes Yes
Functor Types in Various MLs 66 Generative Functors Applicative Functors Higher-order Functors Known! Unsoundness? Standard ML Yes No No No OCaml Yes (Recently) Yes No No Moscow ML Yes Yes Yes Yes
Modularity (= composability through abstraction)! is for reducing cognitive load & strict code dependencies when “programming in the large.”! ! It can be enforced at the linguistic level through type structure ! rather than managed by ad-hoc build system conventions. Takeaway 68