Module type constraints in first class modules

I’ve stumbled into a weird situation recently when using first-class modules. For context, I have to functors with the same type, and I would like to choose one dynamically at run time:

module type BAR = sig type 'a t end
module type FOO = sig module Bar : BAR end

(** 2 functors which look a bit like this *)
module F1(B : BAR) : FOO with module Bar = B = _
module F2(B : BAR) : FOO with module Bar = B = _

The problem comes when trying to choose which functor to us while keeping the information that the resulting FOO module verifies module Bar = B.

Essentially, I would like to be able to do something like this:

let f cond (module B : BAR) =
  let module G = (val 
      if cond then (module F1(B)) else (module F2(B))
      : FOO with module Bar = B) in
  ()  (* G is used here *)

However, this gets rejected with the error message invalid package type: only 'with type t =' constraints are supported. Similarly, the constraint directly on the type also fails with a similar error message:

let f' cond (module B : BAR) =
  let module G = (val 
    if cond then (module F1(B)) else (module F2(B)) 
    : FOO with type 'a Bar.t = 'a B.t) in
  () (* G used here *)

If the argument module B is statically known, then I can do something like this:

module B : BAR = _
module type FOO_WITH_B = FOO with module Bar = B

let f cond =
  let module G = (val 
    if cond then (module F1(B)) else (module F2(B)) 
    : FOO_WITH_B) in
  ()  (* do something with G here *)

Which is a bit ugly but somewhat manageable. However, in my case, the B module is also chosen dynamically… This lead me to the following ugly hack, in which I use a second functor just to generate my signature:

module GenType(B : BAR) = struct
  module type T = FOO with module Bar = B
end

let f cond (module B : BAR) =
  let module G = (val 
    if cond then (module F1(B)) else (module F2(B)) 
    : GenType(B).T) in
  ()  (* G used here*)

Is there any cleaner way to do this?

Also is there any rationale behind the restriction of constraints on packaged module types , or is it just something that wasn’t implemented?

you can do

let f cond (module B : BAR) =
  let module M = (struct module type T = FOO with module Bar = B end) in
  let module G = (val
      if cond then (module F1(B)) else (module F2(B))
      : M.T) in
  ()  (* G is used here *)

I don’t think there’s a let module type so we need an enclosing module M.

The enclosing module can be made anonymous with open struct:

let f cond (module B : BAR) =
  let open (struct module type T = FOO with module Bar = B end) in
  let module G = (val
      if cond then (module F1(B)) else (module F2(B))
      : T) in
  ()  (* G is used here *)
3 Likes