Abstract type that is a polymorphic variant


How can I represent a module interface that defines an abstract type that is supposed to be a polymorphic variant?

module type TextContentF = {
    type t = pri string;
    type errs; /* This is a poly-var */
    let make: string => D.t(t, [> errs]); /* I want to be able to specify that make() can return at least the errors */

I’m getting error:

Error: The type errs does not expand to a polymorphic variant type


Define the polyvariant type in the interface:

type errs = [`err1 | `err2];

See e.g. https://github.com/yawaramin/prometo/blob/ef7229d3cbfe9980e32feef8e882785b49a96434/src/Yawaramin__Prometo.rei#L6


I should mention I’m using this interface for a functor implementation so any text decoding modules implement this interface and can give their own errors. Therefore I do not know the errors ahead of time which is why I’m using an open polymorphic variant.

I’m trying to do Section 4 Functors for extending modules: https://2ality.com/2018/01/functors-reasonml.html

module type TextContentI = {
  type t = pri string;
  type errs;
  let make: string => D.t(t, [> errs]);
module TextContent = (X: TextContentI) => {
  type t = X.t;
  type errs = X.errs;
  let make = X.make;
  let apply = (f, s) => f(s);
  let toJs = (t: t): string => (t :> string);
  let toDb = (t: t): string => (t :> string);

Where TextContentI is some implementation of a string decoder that can return various errors as poly vars.


You don’t need to define all the polyvariant tags ahead of time, just one. This proves to the compiler that it’s a polyvariant type. Users can add more later since the make function’s error type is open.


Ok I had tried something like that and found a way to coerce the types to match but it turns out I had another problem causing the make function to return a closed poly var constraint rather than open.

I was using reazen/relude’s NonEmpty.List module to contain a list of “errs” and I had to implement my own non-empty list type to get it to work.

module type NonEmptyList = {
  type container('a) = list('a); /* If this line is abstract (as in just "type container('a);" it causes problems */
  type t('a) = option(container('a));
  /* ... */

Furthermore my

D.t('a, 'b)

type expanded to

result(('a, array('b)), list('b));

And if array was used to contain 'b, it caused the poly vars to always infer as closed.

So if any of the above were true (if I used array type to contain the polyvars instead of list), it would cause

let make: string => D.t(t, [> errs]);
/* D.t('a, [ `Specific1Error ]) */

Whereas if I changed to list type, removed the abstract type in the interface to explicitly define list, then the type would be inferred as

/* D.t('a, [> `Specific1Error ]) */

Then that lets me do the workaround given in this answer: https://stackoverflow.com/a/53930341


TextContentI is being used as an interface for X, which involves other modules that define their own unique non-intersecting errs type. So I tried

module type TextContentI = {
    type errs = [ | `BaseError ];

But then any module that uses TextContentI type has to have the exact same “type errs;” and it gives a … not included in type errs = [ | `BaseError ]; error.