Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Modularity & Abstraction in Functional Programming

Chris
January 31, 2015

Modularity & Abstraction in Functional Programming

A talk on module systems given at Compose Conference 2015.
http://www.composeconference.org/

Chris

January 31, 2015
Tweet

More Decks by Chris

Other Decks in Programming

Transcript

  1. 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
  2. SML Signatures signature SIG_NAME = sig […declarations…] end Structures* structure

    StructName = struct [...definitions...] end *AKA modules 12
  3. 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
  4. 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
  5. 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
  6. 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
  7. 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
  8. 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
  9. Many Signatures to 1 Structure ListStack : STACK ! ListStack

    : sig val empty : 'a list val push : 'a -> 'a list -> 'a list end 19
  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 val size : 'a stack -> int end Extend the Signature 21
  11. 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
  12. 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
  13. 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
  14. 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
  15. Client code:! ! fn inspect (s : ‘a ListStack.stack) =

    (case s of [] => ... | (x::xs) => ...) Abstraction? 26
  16. structure ListStack : STACK = struct datatype dt = Stack

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

    list [...] end Abstraction through ! Opaque Ascription 28
  18. From Abstraction to Composability SML: Functors!* *not to be confused

    with Haskell functors, Prolog functors, C++ functors, nor category-theoretic functors 29
  19. Functors functor MyFun (Arg : ARGSIG) : MY_SIG = struct

    (...defns possibly mentioning Arg.stuff...) end ! structure MyArg : ARGSIG = ... ! structure MyStruct = MyFun (MyArg) 31
  20. 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
  21. 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
  22. 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
  23. 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
  24. 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
  25. Representation Independence What’s an example of two structures matching the

    same signature that don’t have a relation preserved by their operations? 38
  26. 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
  27. 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
  28. 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
  29. 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
  30. 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
  31. 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
  32. 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
  33. Functors: Other Examples Typeclass-like uses: parameterize a module by values

    inhabiting “comparable” or “equality” types 49
  34. Functors: Other Examples 50 signature ORD = sig type t

    val eq : t -> t -> bool val less : t -> t -> bool end
  35. 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
  36. 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
  37. 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
  38. 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
  39. 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
  40. Logical Content Quantifying over types (System F): ! e :

    ∀ t:type. parametric polymorphism e : ∃ t:type. type abstraction 57
  41. 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
  42. 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.
  43. 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
  44. e.g.
 structure S1 = OrdSet(IntOrd) :> SET structure S2 =

    OrdSet(IntOrd) :> SET A stumbling block? 61 Does S1.add 6 (S2.empty) type check?
  45. 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?
  46. 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 ())
  47. “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)
  48. 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
  49. 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
  50. first-class modules! recursive modules! mix-in modules! ! interpretation of objects

    & typeclasses! package management for Haskell (Backpack) Beyond ML Modules: 67
  51. 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
  52. 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