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.