Context
I want to model the routes of a web application such that it satisfies the following requirements:
- Can enforce complete definitions
- Can create incomplete definitions
- Can check if an incomplete definition "matches" (i.e. is contained in) a complete definition.
As an example to work with:
type root =
| Fruit of fruit
| Veggie of veggie
and fruit =
| Apple of apple
| Banana of banana
and veggie =
| Carrot of carrot
and apple = { diameter: int; cultivar: string; }
and banana = { length: int }
and carrot = { length: int; color: [`Orange | `Purple] }
With this, we can easily create and enforce complete definitions:
let complete = Fruit (Apple { diameter = 8; cultivar = "Golden Delicious" })
But cannot create incomplete definitions
let incomplete = Fruit Apple
^^^^^
Error: The constructor Apple expects 1 argument(s),
but is applied here to 0 argument(s)
And therefore also cannot match incomplete and complete definitions, but we can at least verbosely implement a partial equality function that ignores the parameters:
let equal a b = match a, b with
| Fruit (Apple _), Fruit (Apple _ ) -> true
| Fruit (Apple _), _ -> false
| Fruit (Banana _), Fruit (Banana _ ) -> true
| Fruit (Banana _), _ -> false
| Veggie (Carrot _) , Veggie (Carrot _) -> true
| Veggie (Carrot _) , _ -> false
A vague idea towards a possible solution
So I had this idea of using a tree of GADTs and a heterogeneous list to make the definitions more flexible by having the routes defined as lists, e.g.:
let route = [Fruit; Apple; { diameter = 6; cultivar = "Granny Smith" }]
they could then be used with pattern matching and recursion to more easily destructure and compare them.
Unfortunately implementing this isn't quite so easy. This is what I have so far:
type _ root =
| Fruit : _ fruit root
| Veggie : _ veggie root
and _ fruit =
| Apple : apple fruit
| Banana : banana fruit
and _ veggie =
| Carrot : carrot veggie
and apple = { diameter: int; cultivar: string; }
and banana = { length: int }
and carrot = { length: int; color: [`Orange | `Purple] }
type 'a t =
| [] : _ root t
| ( :: ) : 'b * 'a t -> 'b t
Two problems that I see here:
'bisn't constrained by'a, so anything can be put into the list, as long as it starts with aroot t, and there probably isn't a way to recover the type of the elements either. I think this would require Higher-Kinded Types, but maybe there's a way around that?- Even if I was able to solve that, I'm not sure how I'd be able to terminate it. Perhaps the params could be made into GADTs too, and terminate with
unit.