Here is the canonical example for a rigid type variable escaping its scope:
{-# LANGUAGE RankNTypes #-}
runST :: forall a. (forall s. ST s a) -> a
runST _ = undefined
newRef :: forall s a. a -> ST s (Ref s a)
newRef _ = undefined
runST (newRef 'x') -- type error (rigid a would escape its scope)
I tried to reproduce the error by doing the unification/subsumption manually:
-- goal: unify the expression `runST (newRef 'x')`
fun :: (forall a. (forall b. Foo b a) -> a)
arg :: (forall c. Foo c (Bar c Char))
(forall b. Foo b a0) -> a0 -- meta-ize all tvs bound by the outermost quantifier
Foo s0 (Bar s0 Char) -- skolemize all tvs bound by the outermost quantifier
-- subsumption judgement: offered `arg` is at least as polymorphic as fun's requested argument:
Foo s0 (Bar s0 Char) <: (forall b. Foo b a0)
Foo s0 (Bar s0 Char) <: Foo s1 a0 -- skolemize on the RHS
Foo ~ Foo -- generativity of Foo
s0 ~ s1 -- injectivity of Foo's arguments
-- REJECTED: cannot instantiate `s0` with `s1`
-- we don't even reach the point where the escape check is taken place:
a0 ~ (Bar s0 Char) -- would fail due to s0 ~ s1
To my knowledge a rigid type variable (skolem constant) can only be subsumed under itself or be unified with a meta (unification) type variable that doesn't allow the former to escape its scope, i.e. the meta tv must not be free in the scope where the rigid tv was initially skolemized. Consequently, s0 ~ s1
should be rejected, because two distinct skolems cannot be instantiated, right?
However, as I interpret the error message GHC reaches the escape check. Are my unification steps wrong or do I misinterpet the error message?
Foo a1 (Bar a1 Char)
againstFoo s1 a0
, which givesa1 = s1
anda0 = Bar a1 Char
, and the error is thats1
occurs in the fully substitutedBar s1 Char
. (There is nos0
, I just left your names the same to avoid confusion.) – Jon Purdy