I understand why (1) when the output of a function is a polymorphic variant type, it gets a lower bound from the tags used in the function, and (2) when the input of a function is a polymorphic variant type, it gets an upper bound from the tags matched against if there is no catch-all case. Together these restrictions prevent a function being applied to an argument that it couldn’t deal with:
# let foo x = match x with `Foo -> 0 | `Bar -> 1;;
val foo : [< `Bar | `Foo ] -> int = <fun>
# let baz = `Baz;;
val baz : [> `Baz ] = `Baz
# foo baz;;
Error: This expression has type [> `Baz ]
but an expression was expected of type [< `Bar | `Foo ]
The second variant type does not allow tag(s) `Baz
This is good, because the match in foo
has no clause for Baz
, so it wouldn’t know what to do with that argument.
However, I don’t understand why, when there is a catch-all case in a function, it instead gets a lower bound from the tags matched against.
# let foo2 x = match x with `Foo -> 0 | `Bar -> 1 | _ -> 2;;
val foo2 : [> `Bar | `Foo ] -> int = <fun>
The only effect of this that I can see is to prevent the function from being applied to arguments that it would have no problem dealing with. With this definition of foo2
, we can indeed call
# foo2 `Baz;;
- : int = 2
but a slight modification makes it fail:
# let baz : [`Baz] = `Baz;;
val baz : [ `Baz ] = `Baz
# foo2 baz;;
Error: This expression has type [ `Baz ] but an expression was expected of type
[> `Bar | `Foo ]
The first variant type does not allow tag(s) `Bar, `Foo
Of course, given the inferred typed of foo2
, it makes sense that by constraining the type of baz
I’ve chosen to prevent foo2 baz
from being valid. But why is the type of foo2
constrained in that way? Intuitively, looking at the definition of foo2
it seems that it should be able to deal with any polymorphic variant input at all, and just chooses to do something special with two of the possibilities.
In particular, this behavior means that adding a catch-all case to a function makes certain operations that used to typecheck not typecheck any more.
# let bar : [`Bar] = `Bar;;
val bar : [ `Bar ] = `Bar
# foo bar;;
- : int = 1
# foo2 bar;;
Error: This expression has type [ `Bar ] but an expression was expected of type
[> `Bar | `Foo ]
The first variant type does not allow tag(s) `Foo
This is counterintuitive to me.