Slide 1

Slide 1 text

Modularity & Abstraction in Functional Programming Chris Martens Carnegie Mellon University Compose Conference 2015 1

Slide 2

Slide 2 text

What’s modularity for? How do we get it? (in SML) Type Theory Further Research 2

Slide 3

Slide 3 text

What’s modularity for? ! ! ! 3

Slide 4

Slide 4 text

Abstraction 4

Slide 5

Slide 5 text

Composability 5

Slide 6

Slide 6 text

What’s modularity for? How do we get it? ! ! 6

Slide 7

Slide 7 text

— John Reynolds, 1983 7

Slide 8

Slide 8 text

Inductive Datatypes: Defined by how you build one datatype ‘a list = Nil | Cons of ‘a * ‘a list 8

Slide 9

Slide 9 text

Abstract Datatypes: Defined by how you use one 9

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

SML Signatures signature SIG_NAME = sig […declarations…] end 11

Slide 12

Slide 12 text

SML Signatures signature SIG_NAME = sig […declarations…] end Structures* structure StructName = struct [...definitions...] end *AKA modules 12

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

val s = ListStack.new; val s1 = ListStack.push “foo” s; val s2 = ListStack.push “bar” s1; val x = ListStack.pop s2; - x : string = “bar” Structures 17

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

Many Signatures to 1 Structure ListStack : STACK ! ListStack : sig val empty : 'a list val push : 'a -> 'a list -> 'a list end 19

Slide 20

Slide 20 text

Many Structures to 1 Signature Let’s extend our example… 20

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

Client code:! ! fn inspect (s : ‘a ListStack.stack) = (case s of [] => ... | (x::xs) => ...) Abstraction? 26

Slide 27

Slide 27 text

structure ListStack : STACK = struct datatype dt = Stack of 'a list type t = dt [...] end Abstraction through ! Hidden Datatypes 27

Slide 28

Slide 28 text

structure ListStack :> STACK = struct type t = ‘a list [...] end Abstraction through ! Opaque Ascription 28

Slide 29

Slide 29 text

From Abstraction to Composability SML: Functors!* *not to be confused with Haskell functors, Prolog functors, C++ functors, nor category-theoretic functors 29

Slide 30

Slide 30 text

Functors Parameterized structures expression : function :: module : functor 30

Slide 31

Slide 31 text

Functors functor MyFun (Arg : ARGSIG) : MY_SIG = struct (...defns possibly mentioning Arg.stuff...) end ! structure MyArg : ARGSIG = ... ! structure MyStruct = MyFun (MyArg) 31

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

Functor Application structure Calc1 = RPNCalculator (ListStack) structure Calc2 = RPNCalculator (SizedListStack) 33

Slide 34

Slide 34 text

Representation Independence Reynolds 1983: “Types, Abstraction, and Parametric Polymorphism.” Relational Parametricity, AKA “the abstraction theorem” Mitchell 1986: “Representation independence and data abstraction.” Application of relational parametricity. 34

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

Representation Independence What’s an example of two structures matching the same signature that don’t have a relation preserved by their operations? 38

Slide 39

Slide 39 text

Functors Example: tree traversal parameterized by a data structure to use as a worklist. 39

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

structure Stack : WORKLIST = … Functors Example: Tree Traversal 41

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

structure DFS = Traverse (Stack) structure BFS = Traverse (Queue) Functors Example: Tree Traversal 48

Slide 49

Slide 49 text

Functors: Other Examples Typeclass-like uses: parameterize a module by values inhabiting “comparable” or “equality” types 49

Slide 50

Slide 50 text

Functors: Other Examples 50 signature ORD = sig type t val eq : t -> t -> bool val less : t -> t -> bool end

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

What’s modularity for? How do we get it? (in SML) Type Theory ! 56

Slide 57

Slide 57 text

Logical Content Quantifying over types (System F): ! e : ∀ t:type. parametric polymorphism e : ∃ t:type. type abstraction 57

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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.

Slide 60

Slide 60 text

Q: Suppose you have a functor
 F(X:A) :> sig type t end and a module M:A. ! Does F(M).t = F(M).t ? A stumbling block? 60

Slide 61

Slide 61 text

e.g.
 structure S1 = OrdSet(IntOrd) :> SET structure S2 = OrdSet(IntOrd) :> SET A stumbling block? 61 Does S1.add 6 (S2.empty) type check?

Slide 62

Slide 62 text

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?

Slide 63

Slide 63 text

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 ())

Slide 64

Slide 64 text

“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)

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

first-class modules! recursive modules! mix-in modules! ! interpretation of objects & typeclasses! package management for Haskell (Backpack) Beyond ML Modules: 67

Slide 68

Slide 68 text

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

Slide 69

Slide 69 text

Give ML module systems a try! Takeaway (Alternate) 69

Slide 70

Slide 70 text

Reynolds - Types, Abstraction, & Parametric Polymorphism! Mitchell & Plotkin - Abstract Types Have Existential Type! Mitchell - Representation Independence and Data Abstraction! Derek Dreyer’s thesis & papers! Further Reading Code for this talk: https://github.com/chrisamaphone/compose2015 @chrisamaphone 70