Tuesday, March 7, 2017
Polymorphic variants : Subtyping and variance
Polymorphic variants : Subtyping and variance
Here are some expressions in the top-level involving polymorphic variant types.
# let x = [ `On; `Off ];; val x : [> `Off | `On ] list = [ `On; `Off ]The notation
[> `Off | `On ] represents a type
that at least contains the constructors `Off
and `On. Of course, there are an unlimited number of
such types so [> `Off | `On ] is a set in fact.
# let n = `Number 1;; val n : [> `Number of int ] = `Number 1The value
n is of a type that at least
contains the constructor `Number of int.
# let f = function | `On → 1 | `Off → 0 | `Number n → n;; val f : [< `Number of int | `Off | `On ] → int = <fun>The function
f accomodates exactly three cases
corresponding to the
constructors `Off, `On and `Number
of int. This informs us that [< `Number of int |
`Off | `On ] is of a type that at most has the
constructors `Off, `On and `Number
of int.
The expression ($expr$
:> $typexpr$) coerces the expression $expr$ to
type $typexpr$. The expression ($expr$ :
$typ_{1}$ :> $typ_{2}$ ) coerces the
exprssion $expr$ from $typ_{1}$ to $typ_{2}$. It is only possible
to coerce an expression $expr$ from type $typ_{1}$ to type
$typ_{2},ドル if the type of $expr$ is an instance of $typ_{1}$ and
$typ_{1}$ is a subtype of $typ_{2}$.
# let f x = (x :> [ `A | `B ]);; val f : [< `A | `B ] → [ `A | `B ] = <fun>We see
x needs to be coercible to type [ `A |
`B ]. So, we read [< `A | `B ] as a type
that at most contains the constructors `A
and `B. [ `A ], [ `B ]
and [ `A | `B ] are the subtypes of [ `A | `B
]. It follows then that [< `A | `B ] is
the set of subtypes of [ `A | `B ].
# let f x = (x :> [ `A | `B ] * [ `C | `D ]);; val f : [< `A | `B ] * [< `C | `D ] → [ `A | `B ] * [ `C | `D ] = <fun>We see
x needs to be coercible to [ `A | `B ] *
[ `C | `D ]. This coercion can only proceed
if x designates a pair with first component a subtype
of [ `A | `B ] and second component a subtype
of [ `C | `D ]. So we see, [< `A | `B ] *
[< `C | `D ] is the set of subtypes of [ `A | `B ]
* [ `C | `D ].
# let f x = (x :> [ `A ] → [ `C | `D ]);; val f : ([> `A ] → [< `C | `D ]) → [ `A ] → [ `C | `D ] = <fun>We see
x needs to be coercible to [ `A ] → [
`C | `D ]. This coercion can only proceed if x
designates an arrow where the argument is of a type that at least
contains the constructor `A. That is [ `A
], [ `A | ... ] where the "..."
represent more constructors. From this we see that [>
`A] is the set of supertypes of [ `A ]. The
return value of x (by logic we've already
established) is a subtype of [ `C | `D
]. So, [> `A ] → [< `C | `D ] is the
set of subtypes of [ `A ] → [ `C | `D ].
The transformation represented by f above, has
coerced an arrow x to a new arrow. The type of the
argument of x is [> `A ] and the
type of the argument of the resulting arrow is [ `A
]. That is, for the argument, the transformation between
types has taken a supertype to a subtype. The argument type is
said to be in a "contravariant" position. On the other hand, the
result type of x is [< `C | `D ] and
the arrow that is produced from it, f x, has result
type [ `C | `D ]. That is, the coercion for the
result type has taken a subtype to a supertype : the result type
in x is said to be in "covariant" position.
In the following, the type α t is abstract.
# type α t;; # let f (x : [ `A ] t) = (x :> [ `A | `B ] t);; Error: Type [ `A ] t is not a subtype of [ `A | `B ] tIndeed,
[ `A ] is a subtype of [ `A | `B
] but that, a priori, does not say anything about
the subtyping relationship between [ `A ] t
and [ `A | `B ] t. For this coercion to be legal, the
parameter α must be given a covariant
annotation:
# type +α t;; # let f (x : [ `A ] t) = (x :> [ `A | `B ] t);; val f : [ `A ] t → [ `A | `B ] t = <fun>The declaration
type +α t declares that the
abstract type is covariant in its parameter : if the type $\tau$
is a subtype of $\sigma,ドル then $\tau\;t$ is a subtype of
$\sigma\;t$. Here, $\tau = $[ `A ], $\sigma =
$[ `A | `B ], $\tau$ is a subtype of $\sigma$
and [ `A ] t is a subtype of [ `A | `B]
t.
Here is a similar example, but this time, in the other direction.
# type α t;; # let f (x : [ `A | `B ] t) = (x :> [ `A ] t);; Error: This expression cannot be coerced to type [ `A ] tThe type variable can be annotated as contravariant however, and the coercion function typechecks.
# type -α t;; # let f (x : [`A | `B] t) = (x :> [ `A ] t);; val f : [ `A | `B ] t → [ `A ] t = <fun>The declaration
type -α t declares that the abstract
type t is contravariant in its parameter : if $\tau$
is a subtype of $\sigma$ then $\sigma\;t$ is a subtype of
$\tau\;t$. In this example, $\tau = $[ `A ] and
$\sigma = $[`A | `B], $\tau$ is a subtype of $\sigma$
and
[ `A | `B ] t is a subtype of [ `A ] t.
In the following, type α t is not
abstract and variance can be inferred.
# type α t = {x : α} ;;
# let f x = (x : [`A] t :> [`A | `B] t);;
val f : [ `A ] t → [ `A | `B ] t = <fun>
Introducing a constraint however inhibits variance inference.
# type α t = {x : α} constraint α = [< `A | `B ];;
# let f x = (x : [ `A ] t :> [ `A | `B ] t);;
Error: Type [ `A ] t is not a subtype of [ `A | `B ] t
This situation can be overcome by introducing a covariance
annotation.
# type +α t = {x : α} constraint α = [< `A | `B ];;
# let f x = (x : [ `A ] t :> [ `A | `B ] t);;
val f : [ `A ] t → [ `A | `B ] t = <fun>
In the following example, α does not
participate in the definition of type α t.
# type α t = {x : int};;
# let f x = (x : [ `A ] t :> [ `B ] t);;
val f : [ `A ] t → [ `B ] t = <fun>
In this case, any conversion between δ t
and ε t is legal : the
types δ and ε are not
required to have a subtyping relation between them.