With a GADT of type witnesses, it is possible to write a function that tests for type equality:
type (_, _) eq = Refl : ('a, 'a) eq
type _ ty = Int : int ty | String : string ty
let teq : type a b. a ty -> b ty -> (a, b) eq option =
fun x y -> match (x, y) with
| Int, Int -> Some Refl
| String, String -> Some Refl
| _, _ -> None
Is it possible to do something similar if ty
is an extensible variant, so that a user of the library can define their own types that can then be compared for equality? Of course there are limits on what sort of constructors a user could add and expect things to still work, e.g. if the user says
type _ ty += Any : 'a ty
then I don’t expect there’s going to be any way to extend teq
to deal with those. But if the user restricts themselves to simple constructors like Int
and String
, or perhaps parametrized ones for type formers like
type _ ty +=
| List : 'a ty -> 'a list ty
| Prod: 'a ty * 'b ty -> ('a * 'b) ty
can anything be done?
I can think of two possibilities so far. One is to maintain a function like teq
, or a list of them, in a mutable global variable, so that when users add constructors to ty
they can “add clauses” to the match statement by adding new functions to this variable. This would be extra boilerplate for the user, but it would also exclude mistakes like Any
since it wouldn’t be possible to write the corresponding clause. (Although it might require some cleverness to make this “extensible function” recursive enough to deal with List
and Prod
.)
The other possibility I wonder about is
type wrap = Wrap : 'a -> wrap
let teq : type a b. a ty -> b ty -> (a, b) eq option =
fun x y -> if Wrap x = Wrap y then Some (Obj.magic Refl) else None
On a previous question I learned that comparing elements of wrap
for “structural equality” doesn’t see the types. But according to my current understanding, it seems that this comparison would still do the right thing on constructors such as Int
, String
, List
, and Prod
, since the type is reified in the constructors. Of course it would be wrong in the presence of a constructor like Any
, in which case the Obj.magic
would be actually ill-typed and cause all sorts of mayhem, so this is probably a Bad Idea in general. But in the interests of improving my understanding of OCaml, I’m curious whether this would at least work in the case when all constructors of ty
are “good” ones.