Use of "module type" and module files

I’m trying to create a container for values with some shared behaviours. This is the setup boiled down to an absolute minimum.

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

module Int_thing : (Thing with type t = int) = struct
  type t = int
  let compare = Int.compare
end

module Bool_thing : (Thing with type t = bool) = struct
  type t = bool
  let compare = Bool.compare
end

module Range (M: Thing) = struct
  type t = {lower: M.t; upper: M.t}

  let is_valid (rng:t) : bool = M.compare rng.lower rng.upper <= 0
end

module Int_range = Range(Int_thing)

let r0 : Int_range.t = { lower=10; upper=20 }
let r0_is_valid = Int_range.is_valid r0

So that all works fine in a single file with utop - I can create ranges of different things and the “with” lets me easily construct “Things” where they are a simple wrapper around a built-in type.

But - I’m failing at what feels like the simple task of splitting that up into separate files. My googling failed me and I can’t see any examples in the source of a couple of libraries I downloaded.

The problem I think boils down to two questions.

Firstly - Thing. I think I have to embed the “module type” inside a separate module - it can’t be it’s own file. So: something.mli and then reference it as Something.Thing. Is that correct?

Secondly - how do I then reference Thing in bool_thing.mli and similar? I couldn’t find the right incantation and ended up just copying+pasting the signature for each concrete module.

TIA

(* bool_thing.mli *)
include Something.Thing with type t = bool
1 Like

Excellent - that does it.

It makes sense too when you think about it - we are already effectively “inside” the module interface.

Thanks very much.