CSCI 2041

ADVANCED PROGRAMMING PRINCIPLES

Functors

last time

let build_sloq n =
  let rec qbuild i q = if i=0 then q
    else qbuild (i-1) (Queue.enqueue q i)
  in qbuild n Queue.empty_q
let rec empty_sloq q = if Queue.is_empty q then ()
  else let q' = Queue.dequeue q in
  empty_sloq q'
let build_fq n =
  let rec qbuild i q = if i=0 then q
  else qbuild (i-1) (FastQ.enqueue q i)
  in qbuild FastQ.empty_q
let rec empty_fq q = if FastQ.is_empty q then ()
  else let q' = FastQ.dequeue q in
  empty_fq q'

This looks like an opportunity for code reuse…

Functors

A functor is a module that acts on one or more other modules as arguments:

module TestQ (TQ : QSig) = struct
 let build_long_q n =
   let rec qbuild i q = if i=0 then q
    else qbuild (i-1) (TQ.enqueue q i)
   in qbuild n TQ.empty_q
 let rec empty_q q = if TQ.is_empty q then ()
    else let q’ = TQ.dequeue q in
   empty_q q’
end
module FQTest = TestQ(FastQ)
FQTest.build_long_q (1 lsl 14)
module SloQTest = TestQ(Queue)
SloQTest.build_long_q (1 lsl 14)
module LQT = TestQ(LeakyQ)

Module typing is “structural”:

module SlackerQ = struct
 type 'a q = Nevs of 'a | Whatevs
 let empty_q = Whatevs
 let is_empty q = true
 let enqueue q x = Whatevs
 exception OMG
 let peek q = raise OMG
 let dequeue q = Whatevs
 exception EmptyQ = OMG
end
module SQTest = TestQ(SlackerQ)

Module M matches signature S if it has the minimal functionality (matching names, types) required by S.

Signatures and Structures can be anonymous

module F(X : sig val f : int -> int end)
= struct
let f = X.f
  let x = X.f 10
end
module FX = F(struct let f n = n*3 end)
let module M : sig type t val x : t end
  = struct type t = int let x = 17 end
in ()

But they are not “first-class” types and values:

# let X = struct let x = 10 end
Error: Syntax error
# F(struct let f n = n*3 end)
Error: Syntax error: operator expected.

Functors offer another approach to polymorphism:

module type OrdSig = sig
 type t
 val compare : t -> t -> int
end
module BSTree (Item : OrdSig) =
struct
 type elt = Item.t
 type t = Empty | Node of elt*t*t
 let empty = Empty
 let rec search t x = (* some stuff *)
 let rec insert t x = (* more stuff *)
 let rec fold f acc t = (* you know *)
end
module StringBST = BSTree(String)

sharing

Suppose we want to make the BSTree.t type abstract…

module type BST = sig
  type elt
  type t
  val empty : t
  val search : t -> elt -> bool
  val insert : t -> elt -> t
end
module BSTree (Item : OrdSig) : BST = (* all that stuff before *)
module StringBST = BSTree(String)
StringBST.insert StringBST.empty "wuuuh?"

Problem: BST.elt is abstract. It can be “opened” with a sharing constraint:

module BSTree (Item : ordered) : BST
with type elt = Item.t =
struct
(* all that stuff a third time *)
end
module StringBST = BSTree(String)
StringBST.insert StringBST.empty "wuuuh?"

How would we use the BSTree functor to make a case-insensitive BSTree?

OCaml’s Set.Make, Map.Make functors work similarly…

cs2041.org

// reveal.js plugins