Skip to article frontmatterSkip to article content

Code Reuse with Modules

Cornell University

From Textbook: Code Reuse with Modules


Includes

Def: includes enables a structure to include all the values defined by another structure, or a signature to include all the names declared by another signature.

Syntax

module type SetExtended = sig
  include Set
  (*all other definitions specific to SetExtended*)
  val of_list : 'a list -> 'a t
end

module ListSetDupsExtended = struct
  include ListSetDups
  (*all other definitions specific to SetExtended*)
  let of_list lst = List.fold_right add lst empty
end

Encapsulation

module ListSetDupsImpl = struct
  type 'a t   = 'a list
  let empty   = []
  let mem     = List.mem
  let add x s = x::s
  let elts s  = List.sort_uniq Stdlib.compare s
end

module ListSetDups : Set = ListSetDupsImpl

module ListSetDupsExtended = struct
  include ListSetDupsImpl
  let of_list lst = lst
end

The important change is that ListSetDupsImpl is not sealed, so its type 'a t is not abstract. Plus, OCaml compiler can infer it is an implementation of Set .When we include it in ListSetDupsExtended, we can therefore exploit the fact that it’s a synonym for 'a list.

The clients should use ListSetDups, but when we use List to implement other things, we should use ListSetDupsImpl instead.

Includes vs. Open

module M = struct
  let x = 0
end

module N = struct
  include M
  let y = x + 1
  let z = 1
end

module O = struct
  open M
  let y = x + 1
  let z = 1
end


module M : sig val x : int end
module N : sig val x : int val y : int val z : int end
module O : sig val y : int val z : int end

N has both an x and y, whereas O has only a y. The reason is that include M causes all the definitions of M to also be included in N, so the definition of x from M is present in N. But open M only made those definitions available in the scope of O, aka. a part of the implementation; it doesn’t actually make them part of the structure, aka. the client cannot see them. So O does not contain a definition of x, even though x is in scope during the evaluation of O’s definition of y.

Functors

Def: a functor is simply a “function” from structures to structures. It is a parametrized module.

Syntax

module F (M : S) = struct
  ...
end

(*annonymous functors*)
module F = functor (M : S) -> struct
  ...
end

(*functors parametriezed with multiple modules*)
module F (M1 : S1) ... (Mn : Sn) = struct
  ...
end

(* above are the desugared version of the codes above*)
module F = functor (M1 : S1) -> ... -> functor (Mn : Sn) -> struct
  ...
end
module ANewModule = F(OldModule)
module ListSetNoDupsExtended = ExtendSet(ListSetNoDups)

Application

Extension

module ExtendSet(S:Set) = struct
  include S

  let add_all lst set =
    let add' s x = S.add x s in
    List.fold_left add' set lst
end

Other than Extension: Testing

module SackTester (S: StackSig) = struct
	let _ = assert (S.(empty |> push 1 |> peek) = 1)
end

module MyStackTester = StackTester(MyStack)
module ListStackTester = StackTester(ListStack)

The only difference is that because the latter example is about extension, we need to include everything from its parent module.