I usually think of two module types S1 and S2 as being equivalent if the
following two functors type check:
module F12 (M : S1) = (M : S2)
module F21 (M : S2) = (M : S1)
And by equivalent, I mean indisinguishable – one should be able to use S1
anywhere one uses S2, and vice versa, with exactly the same type checker and
semantic behavior.
However, I found an an example today with two module types that are equivalent,
both in my internal mental model of equivalence and in my formal definition that
F12 and F21 type check, but that one can distinguish using module type of.
Here are the module types:
module type S1 = sig module N : sig type t end type u = N.t end
module type S2 = sig
type u
module N : sig
type t = u
end
end
module F12 (M : S1) = (M : S2)
module F21 (M : S2) = (M : S1)
And here is a context that distinguishes them: F1 type checks, but F2 does
not:
module F1 (A : S1) = struct
module F (B : sig type t end) = (B : module type of A.N)
end
module F2 (A : S2) = struct
module F (B : sig type t end) = (B : module type of A.N)
end
What’s going on is that in F1, module type of A.N decides to abstract t,
because it doesn’t have a definition. But in F2, module type of A.N does not
abstract t, because it is defined to be u.
Since I thought of S1 and S2 as equivalent, I would have preferred that
module type of not abstract t in both cases, and thus that both F1 and
F2 be rejected. But I don’t see anything unsound about what OCaml is doing.