I’m tooling with the implementation of modules with a given signature.
Let’s say I have a module type with this signature :
module type T = sig
I can provide an implementation and defining the type :
module M_string: T with type t = string = struct type t = string end
module M_variant: T with type t = [`OK] = struct type t = [`OK] end
but this does not work with enum types or gadt :
module M2: T with type t = | OK = struct type t = | OK end
9 | module M2: T with type t = | OK = struct type t = | OK end
Error: Syntax error
Is there an easy solution ? (I’ve simplified the example, but I’m actually including other signatures and I don’t want to rewrite the full signature).
Sum types (including GADTs) need to be declared (given a name), so the best you can do is to define the type outside the module and refer to it from both places, eg:
type foo = ...
module M : T with type t = foo = struct type t = foo end
But actually, depending on what you are doing, you may want to leave
: T with type t = foo out altogether, and rely on the compiler to check the module signature for you.
You can use
include rather than
with to relate the signature of
module M2: sig
type t = OK
include T with type t := t
type t = OK
I didn’t thought of the destructive substitution.
The specification explicitaly mention that “There are no type expressions describing (defined) variant types nor record types, since those are always named, i.e. defined before use and referred to by name.” [https://caml.inria.fr/pub/docs/manual-ocaml/types.html#sss:typexpr-variant-record] But I do not understand the rationale behind this, as the polymorphic variants does not follow the same scheme.
Is this an old specification ?
Polymorphic variants aren’t defined. If you define two variants with the same constructors, like this:
type s = A | B
type t = A | B
then you end up with two incompatible types, since each variant definition creates a fresh type.
However, if you write something similar for polymorphic variants
type s = [`A | `B]
type t = [`A | `B]
then you haven’t created anything new:
t are just aliases for equivalent type expressions.
If you ask why the manual says this even though polymorphic variants exist, it’s because that sentence is not talking about polymorphic variants I guess.
If you ask why variants and records behave that way: well, both behaviours are useful. Structural types (like objects and polymorphic variants) are more flexible and allow for nice tricks in various cases. Nominal types (like variants and records) are better-behaved; with nominal types, the type-checker tends to catch more bugs and give better error messages.