Extending types


#1

I have a specific need, I would like to extend a type where in simple terms:

module A = {
  type t;

  type body('a) = {.
    name: string,
    contents: 'a
  };

  make:  unit => t;
};


module B = {
  type t('a) = A.t;

  type body('a) = {.
    surname: string
  };

  make: unit => t('a);
};

type body in the module B would be extended by the surname field in the module B. What is the way to do this?

In the newer OCaml this seems to be possible via extension types:

Module A = struct
  type foo = Hey
end

Module B = struct
  type foo += Ho
end

#2

“Extension types” doesn’t do what you want – that just adds another constructor to a variant.
There’s no way in ocaml to “extend” a record type… I generally end up doing one of two things:

Nesting

type body = {name: string};
type betterBody = {body, surname: string};

Or providing an extension point

type body('extra) = {name: string, extra: 'extra};
type bodyExtra = {surname: string};
type betterBody = body(bodyExtra);

#3

Extension point seems to be the way to go. It is not super clean but I guess its the only way without having some intersection handled like in flow

type a = { name: string } & b


#4

I don’t think the difference between nesting and extension points is that huge. They both describe a nested record instead of a flat one.

But then, I don’t know, maybe that’s a feature, because it’s kind of like composition over inheritance and there’s no place for extending interfaces product types in ML.


#5

I think the rationale is same as with untagged union string | number as in algebraic type systems it is difficult make it work in a safe way.

How much of a feature it actually is I don’t know, it makes a lot of relatively simple things much harder definitely.


#6

Well, I’m used to using Some & { more: ... }-like types in TypeScript as well, but then I was bitten a few times with flat React props combined with HOCs, so maybe a bit of nesting is actually a good thing.


#7

Yes but in this particular case nesting just cannot work due to mapping external node library that is a plugin to another library and extension happens by extending callback’s returned object.

This is how it typically functions in node ecosystem.