Heterogeneous list using GADTs?

Hello, I’m trying to construct a GADT that is creating two separate values and the goal is that finally the types should match up and be the same. So the GADT has a structure:

type expr('a,'b) = 
  | Custom(string => option('a)): expr(('a => 'b, 'c), ('b, 'c))
  | Slash(expr(('a, 'b), ('c, 'd)), expr(('c, 'd), ('e, 'f))) // combine two expr
      : expr(('a, 'b), ('e, 'f))
   // ...and a bunch of other cases omitted

And the evaluator for expr, which I do like this:

 let rec evaluator:
    type a b c d.
      (expr((a, b), (c, d)), endpoint(a, b)) => Result.t(endpoint(c, d)) =
          (route, state) =>  switch (route) { ... }

Next up I want to create a way to enforce that eval always creates endpoints of type endpoint(a,a). I can do this by defining a function

  let completeAttempt:
    type a b c.
      (expr((a, b), (c, c)), endpoint(a, b)) => Result.t(endpoint(c, c)) =
    (route, state) => attempt(route, state);

This way I can encode the result with a function encode: type a. endpoint(a,a) => string. Now to my question: I want to combine different endpoint(a,a). My first idea is to use a existential wrapper:

 // Existential wrapper
  type any =
    | Any(expr(_)): any;

 type eval('a, 'b) =
    | OneOf(list(any)): eval('a, Response.t(string));

As you notice OneOf takes a list of any expr. However it has no way of knowing if the expression has a type expr('a,'a), meaning that later when I evaluate the expression it will throw a compiler error:

  let evaluate:
    type a b.
      (eval((a, b), Response.t(string)), endpoint(a, b)) =>
      Response.t(string) =
    (evalExpr, endpoint) =>
      switch (evalExpr) {
      | OneOf(expr) =>
         // It can't verify that p has type ('c,'c), thus it throws an error
        List.map((Any(p)) => completeAttempt(p, endpoint), expr) 
        |> pickFirstSuccess
        |> encode

//...

I feel my approach might be wrong. I can also imagine a syntax using some alternative operator instead of a list to construct this and use Either to find the first successful option.

As far as I can see, the type of eval that you wanted to write is simply:

type eval('a) = list(expr('a,'a))

It is not clear why you are trying to erase all type information in the current definition of eval.