Unfortunately, it is not really possible to extend polyvariants with arbitrary cases within a functor.
I wish I had a better grasp on the fundamental issue here, but I know it’s related to the fact that functors require the output module signature to be fully-defined, while polyvariants cannot be universally quantified.
I’ll try to explain it to the best of my ability, but I know there are others here who understand this better than me. So if I make a mistake, please let me know!
For a polyvariant type to be fully defined (meeting the functor requirement), the entire polyvariant type, including the
extension type, must be fully defined prior to its final definition in the output module. We cannot use a type variable to represent an arbitrary polyvariant type, where the final type is computed at the place where the functor is applied. This is because the polyvariant’s “constraints” must be fully defined in the module signature. Those constraints cannot be defined dynamically. More on “constraints” further down in this post.
let fun1: ('a) => [ defaultColor | 'a ] = ...
You should see a syntax error here.
'a cannot be included in the polyvariant type because it cannot be assumed to be a polyvariant at all. It could be anything.
let fun2: type a. (a) => [ defaultColor | a ] = ...
This uses “universal quantification.” (The
type a. part defines a “universally quantified type”
a, which is like saying "for all possible types
a".) You should get a compile error saying type
a is not a polymorphic variant. This is because polyvariants cannot be universally quantified.
type purplePlus<'a> = [> #Purple] as 'a;
let fun3: (minimalColor<'a>) => [ minimalColor<'a> | purplePlus<'a> ] as 'a = ...
'a here is not a “type variable”, but rather a “unification variable” that is known to be a polyvariant that must meet all the constraints implied by each type that uses it. This includes
minimalColor<'a>, which constrains type
'a to include "at least
[ #Red | #Green | #Blue ]". But type
'a is also constrained by the definition of
purplePlus<'a>, which requires it to include "at least
[ #Purple ]".
The final, “unified” type denoted by
'a must adhere to all of those constraints, so it will look like
[> | #Red | #Green | #Blue | #Purple ]. The nice thing is that the unification variable
'a can be determined at the function call site. I think this might help you achieve the kind of “type extension” that you want.
For related reading, see this discussion in the OCaml forum: https://discuss.ocaml.org/t/polymorphic-variant-being-generalised/6083/3
It’s also worth mentioning “extensible variants.” It’s a more complicated kind of variant that can be arbitrarily extended at any point in your code. But it is also subject to restrictions when used within functors.
The syntax for extensible variants looks like this:
type t += ..
type t +=
// ...further down in the code...
type t +=
// Now we have added the `Purple` case to type `t`
This is an advanced technique. It tends to add unnecessary complexity, and it’s usually not worth it. I recommend staying away from extensible variants unless you have a really solid use case.