-
Notifications
You must be signed in to change notification settings - Fork 0
Bikeshed function subtyping wrt regions
This subtyping relationship should hold:
fn<&x>(&x.T) -> T' <: fn<&y>(&y.S) -> S'
Here, x
and y
are two region variables being introduced for the
first time, and it's irrelevant what the types T
, T'
, S
, and
S'
are.
This relationship:
fn<&x>(&x.T) -> T' <: fn(&y.S) -> S'
should also hold, because in this case, we ought to be able to use
functions that have the subtype in any situation where we can use
function that have the supertype (since y
is a free variable
representing a particular region, and x
is a bound variable
representing all regions -- a set that includes y
).
But this relationship:
fn(&x.T) -> T' <: fn<&y>(&y.S) -> S'
(where x
is free and y
is bound) should not hold, because here
the alleged subtype is a function that only accepts a pointer with the
lifetime x
, and the alleged supertype accepts a pointer with any
lifetime. So we can't use the subtype in any context that expects the
supertype (because the context might want to pass in any old pointer).
We can enforce the above subtype relationships using the following algorithm:
- Instantiate each bound region in the subtype with a fresh region variable.
- Instantiate each bound region in the supertype with a fresh concrete region.
- Now see if the subtyping relationship holds (performing unification and so on).
Using this algorithm on the first example above, we come up with
fn(&X.T) -> T' <: fn(&y'.S) -> S'
where X
is a fresh region variable and y'
is a fresh concrete
region. Then we check if that holds. It ought to, because X
and
y'
should unify.
For the second example above, we come up with
fn(&X.T) -> T' <: fn(&y.S) -> S'
where X
is a fresh region variable. Then we check if that holds.
It ought to, because X
and y
should unify. (They unify even
though y
is a "real" concrete region, because there's no reason why
it shouldn't unify with a fresh region variable!)
In fact, as an optimization, in the first example we don't actually
have to create a fresh concrete region (that is, y'
) in the
supertype. That seems to make sense, because then it would just work
like the second example. Niko pointed out that this trick is
"skolemization". In trying to figure out what this has to do with
skolemization as I understand it (basically, getting rid of
existential quantifiers and creating new terms to replace them -- see
a note about this below), it occurred to me that maybe there's an
implicit existential binder around the types that have free region
variables above. So, if y is one such, then maybe this is the
optimization discussed
here,
where "only variables that are free in the formula are placed in the
skolem term". I'm not really sure yet.
Anyway, in the third example above, there are no bound regions in the subtype, and there's a bound region in the supertype. So we get:
fn(&x.T) -> T' <: fn(&y'.S) -> S'
where y'
is a fresh concrete region. Unification fails, because x
and y'
are just different concrete regions and can't unify with each
other.
This section is more or less a gloss of the Wikipedia article.
Skolemization is the process of getting rid of existential quantifiers
and creating new terms to replace them. In the simplest case (if
there aren't any universal quantifiers to contend with), exists x. P(x)
can change to simply P(c)
where c
is a new constant.
This makes sense -- c
is just the x
that we know exists.
If there are some universal quantifiers around the thing -- say,
forall x. exists y. forall z. P(x, y, z)
-- then skolemization gives
us forall x. forall z. P(x, f(x), z)
, where f
is a new function
that takes x
as its argument. f
has to take x
as an argument
because x
was bound by the universal quantifier that was outside the
existential quantifier that was removed. This technique generalizes
to any nesting of universal and existential quantifiers.