How to reference like-named/cyclic types in shared contexts


#1

I have some module context(s) defined like this, where both Parent and Child are required to have a type t to meet some interface:

module Parent = {
  type t = {foo: int};

  module Child = {
    type t = {bar: int};

    let fromParent: t => t = ({foo}) => {bar: foo};
  };
};

How can I define the signature for fromParent since both types are named t? I remember coming across a way to describe this when dealing with something like Cmp.t = option(Parent.t), but I no longer have access to that codebase and I don’t recall where in the (ocaml? reasonml? bucklescript?) docs I came across the solution.

Any hints would be appreciated.


#2

You can alias it:

module Parent = {
  type parent = {foo: int};
  type t = parent;

  module Child = {
    type t = {bar: int};
    let fromParent: parent => t = ({foo}) => {bar: foo};
  };
};

But you actually don’t need the type annotation at all, because the parent module’s record fields are visible from the child module:

module Parent = {
  type t = {foo: int};

  module Child = {
    type t = {bar: int};
    let fromParent({foo: bar}) = {bar};
  };
};

#3

In this case annotation isn’t the issue, it’s the actual definition of type t = t, but annotation seemed like an easier way to frame the issue so as to potentially get more answers.

The more realistic case is where I’m using a Belt.Map and I need to create a Cmp module which also has a type t:

module Parent: ModuleWithTypeT = {
  type t = {foo: int};

  module Cmp = {
    include Belt.Id.MakeComparable({
      type t = option(t);

      let cmp = (x, y) =>
        liftA2(({foo: left}, {foo: right}) => left - right, x, y)
        |> getWithDefault(0);
    });
  };
};

In the past I’ve just re-aliased the initial t to some other variable, but again, I later found another approach which makes the aliasing unnecessary. Also again, I know longer have access to the code where this second approach was taken and my memory is too rusty to remember how that solution was described.

In short: I am 1000% certain that there is a way to define a type based on a type with the same name that does not involve a redundant intermediate type, but I forget what the approach is called. Does anyone recall what that is called?


#4

Oh, this is actually a slightly different question. In this case you can do:

type nonrec t = option(t);

Types are recursive by default but you can make them non-recursive to refer to a previously-defined type.


#5

Fantastic @yawaramin! I knew there was a way to do it, I just wasn’t sure how to formulate the question .