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

Modularity & Abstraction in Functional Programming

B6ff5f798c18a3367b2770aa3ada0730?s=47 Chris
January 31, 2015

Modularity & Abstraction in Functional Programming

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

B6ff5f798c18a3367b2770aa3ada0730?s=128

Chris

January 31, 2015
Tweet

More Decks by Chris

Other Decks in Programming

Transcript

  1. Modularity & Abstraction in Functional Programming Chris Martens Carnegie Mellon

    University Compose Conference 2015 1
  2. What’s modularity for? How do we get it? (in SML)

    Type Theory Further Research 2
  3. What’s modularity for? ! ! ! 3

  4. Abstraction 4

  5. Composability 5

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

    6
  7. — John Reynolds, 1983 7

  8. Inductive Datatypes: Defined by how you build one datatype ‘a

    list = Nil | Cons of ‘a * ‘a list 8
  9. Abstract Datatypes: Defined by how you use one 9

  10. 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
  11. SML Signatures signature SIG_NAME = sig […declarations…] end 11

  12. SML Signatures signature SIG_NAME = sig […declarations…] end Structures* structure

    StructName = struct [...definitions...] end *AKA modules 12
  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 13
  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 14
  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 15
  16. 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
  17. 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
  18. 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
  19. Many Signatures to 1 Structure ListStack : STACK ! ListStack

    : sig val empty : 'a list val push : 'a -> 'a list -> 'a list end 19
  20. Many Structures to 1 Signature Let’s extend our example… 20

  21. 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
  22. 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
  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 23
  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 24
  25. 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
  26. Client code:! ! fn inspect (s : ‘a ListStack.stack) =

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

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

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

    with Haskell functors, Prolog functors, C++ functors, nor category-theoretic functors 29
  30. Functors Parameterized structures expression : function :: module : functor

    30
  31. Functors functor MyFun (Arg : ARGSIG) : MY_SIG = struct

    (...defns possibly mentioning Arg.stuff...) end ! structure MyArg : ARGSIG = ... ! structure MyStruct = MyFun (MyArg) 31
  32. 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
  33. Functor Application structure Calc1 = RPNCalculator (ListStack) structure Calc2 =

    RPNCalculator (SizedListStack) 33
  34. 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
  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 35
  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 M ~ M’ => for all F, F(M) ~ F(M’) ! 36
  37. 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
  38. Representation Independence What’s an example of two structures matching the

    same signature that don’t have a relation preserved by their operations? 38
  39. Functors Example: tree traversal parameterized by a data structure to

    use as a worklist. 39
  40. 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
  41. structure Stack : WORKLIST = … Functors Example: Tree Traversal

    41
  42. 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
  43. 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
  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 44
  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 45
  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 46
  47. 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
  48. structure DFS = Traverse (Stack) structure BFS = Traverse (Queue)

    Functors Example: Tree Traversal 48
  49. Functors: Other Examples Typeclass-like uses: parameterize a module by values

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

    val eq : t -> t -> bool val less : t -> t -> bool end
  51. 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
  52. 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
  53. 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
  54. 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
  55. 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
  56. What’s modularity for? How do we get it? (in SML)

    Type Theory ! 56
  57. Logical Content Quantifying over types (System F): ! e :

    ∀ t:type. parametric polymorphism e : ∃ t:type. type abstraction 57
  58. 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
  59. 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.
  60. 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
  61. e.g.
 structure S1 = OrdSet(IntOrd) :> SET structure S2 =

    OrdSet(IntOrd) :> SET A stumbling block? 61 Does S1.add 6 (S2.empty) type check?
  62. 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?
  63. 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 ())
  64. “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)
  65. 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
  66. 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
  67. first-class modules! recursive modules! mix-in modules! ! interpretation of objects

    & typeclasses! package management for Haskell (Backpack) Beyond ML Modules: 67
  68. 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
  69. Give ML module systems a try! Takeaway (Alternate) 69

  70. 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