Ahh, yes, I’ve run into this problem myself as well.
I’m not sure if this is the best solution, because it requires explicitly having extra cases in your definition, but if what you care about is the resulting type, then this does fit the criterion:
let f: ([< `foo | `bar ] as 'a) t -> string = fun (type a) (vl: a t) ->
match vl with
| Foo -> "foo"
| Bar -> "bar"
| _ -> failwith "unsupported"
I’d be curious to know if there is a good/better way to solve it.
# type 'a t =
| Foo : [> `foo ] t
| Bar : [> `bar ] t
| Baz : [> `baz ] t ;;
# let f : [< `foo | `bar ] t -> string = function
| Foo -> "foo"
| Bar -> "bar" ;;
val f : [ `bar | `foo ] t -> string = <fun>
# f Foo ;;
- : string = "foo"
# f Bar ;;
- : string = "bar"
# f Baz ;;
Error: This expression has type [> `baz ] t
but an expression was expected of type [ `bar | `foo ] t
The second variant type does not allow tag(s) `baz
This approach works too. It allows you to keep the Foo, etc., constructors monomorphic. The foo_bar type is an implementation detail of f and can be hidden behind a module type.
type 'a t =
| Foo : [ `foo ] t
| Bar : [ `bar ] t
| Baz : [ `baz ] t
type foo_bar = Foo_bar : [< `foo | `bar ] t -> foo_bar
let f x =
let Foo_bar x = Foo_bar x in
match x with
| Foo -> "foo"
| Bar -> "bar"
(* val f : [< `bar | `foo ] t -> string = <fun> *)