Search code examples
moduleocamlencapsulationfunctor

OCaml use signature defined outside functor to limit visibility into produced module


I'm trying to write a functor that takes a pair of ordered things and produces another ordered thing (with ordering defined lexicographically).

However, I want the resulting "ordered type" to be abstract, rather than an OCaml tuple.

This is easy enough to do with an inline/anonymous signature.

(* orderedPairSetInlineSig.ml *)

module type ORDERED_TYPE = sig
  type t
  val compare : t -> t -> int
end

module MakeOrderedPairSet (X : ORDERED_TYPE) :
  sig
    type t
    val get_fst : t -> X.t
    val get_snd : t -> X.t
    val make : X.t -> X.t -> t
    val compare : t -> t -> int
  end = struct
    type t = X.t * X.t

    let combine_comparisons fst snd =
      if fst = 0 then snd else fst

    let compare (x, y) (a, b) =
      let cmp  = X.compare x a in
      let cmp' = X.compare y b in
      combine_comparisons cmp cmp'

    let get_fst ((x, y) : t) = x

    let get_snd ((x, y) : t) = y

    let make x y = (x, y)
  end

I want to give my anonymous signature a name like ORDERED_PAIR_SET_TYPE and move it outside the definition of MakeOrderedPairSet, like so (warning: not syntactically valid) :

(* orderedPairSet.ml *)

module type ORDERED_TYPE = sig
  type t
  val compare : t -> t -> int
end

module type ORDERED_PAIR_SET_TYPE = sig
  type t
  type el

  val get_fst : t -> el
  val get_snd : t -> el
  val make : el -> el -> t

  val compare : t -> t -> int
end

module MakeOrderedPairSet (X : ORDERED_TYPE) :
  (ORDERED_PAIR_SET_TYPE with type el = X.t) = struct
    type t = X.t * X.t

    let combine_comparisons fst snd =
      if fst = 0 then snd else fst

    let compare (x, y) (a, b) =
      let cmp  = X.compare x a in
      let cmp' = X.compare y b in
      combine_comparisons cmp cmp'

    let get_fst ((x, y) : t) = x

    let get_snd ((x, y) : t) = y

    let make x y = (x, y)
  end

with el being an abstract type in the signature that I'm trying to bind to X.t inside the body of MakeOrderedPairSet.

However, I can't figure out how to fit everything together.

(ORDERED_PAIR_SET_TYPE with type el = X.t) is the most obvious way I can think of to say "give me a signature that's just like this one, but with an abstract type replaced with a concrete one (or differently-abstract in this case)". However, it isn't syntactically valid in this case (because of the parentheses). Taking the parentheses off does not result in a valid "module-language-level expression" either; I left it on because I think it makes my intent more obvious.

So ... how do you use a named signature to restrict the visibility into a [module produced by a functor]/[parameterized module]?


Solution

  • If you don't want to add el to the exports of the module then there are two ways:

    1. Use a substitution constraint:

      ORDERED_PAIR_SET_TYPE with type el := X.t
      

      That will remove the specification of el from the signature.

    2. Use a parameterised signature. Unfortunately, that is not expressible directly in OCaml, but requires a bit of extra functor gymnastics around the definition of your signature:

      module SET_TYPE (X : ORDERED_TYPE) =
      struct
        module type S =
          sig
            type t
            val get_fst : t -> X.el
            val get_snd : t -> X.el
            val make : X.el -> X.el -> t
            val compare : t -> t -> int
          end
        end
      

      With that you can write:

      module MakeOrderedPairSet (X : ORDERED_TYPE) : SET_TYPE(X).S = ...