Reputation: 33
In GHC, the equality instance for IORef
and STRef
are based on the following primitive operation:
sameMutVar# :: MutVar# s a -> MutVar# s a -> Int#
I would like to be able to compute a heterogeneous reference equality,
sameReference :: IORef a -> IORef b -> Bool
(or similarly for STRef
s with potentially different types). Is it okay to just use unsafeCoerce
for the purpose of checking the reference equality? Is there a reason that sameMutVar#
is not given a heterogenous type signature?
EDIT: To add some context, I would like to have this hetereogenous pointer equality because I want to use the equality method to remove a particular IORef a
from a list of IORef
s whose types are existentially quantified over.
Upvotes: 3
Views: 292
Reputation: 48581
It is perfectly safe to write
sameReference :: IORef a -> IORef b -> Bool
sameReference = unsafeCoerce ((==) :: IORef a -> IORef a -> Bool)
It would have been entirely reasonable for the primop to have been given type
sameMutVar# :: MutVar# s a -> MutVar# s b -> Int#
but the designers apparently felt that using that function on references of different types was more likely to be a mistake than otherwise.
What you can't do safely is conclude that sameReference (r1 :: IORef a) (r2 :: IORef b) = True
implies that a
and b
are the same. Suppose you had
sameRefSameType :: IORef a -> IORef b -> Maybe (a :~: b)
Then you could easily write
oops :: Coercible a b => IORef a -> a :~: b
oops r = fromJust (sameRefSameType r (coerce r))
producing bogus evidence that any two coercible types are equal. You should be able to figure out how to use a GADT to get from there to mkUC :: IO (a -> b)
.
I believe that it would be safe to write
sameRefCoercibleTypes :: IORef a -> IORef b -> Maybe (Coercion a b)
Since Daniel Wagner mentioned stable names, I should mention that the situation for those is even worse in this context. I'll need to start with a bit of background. Suppose you write
f :: Either x Int -> Either x Bool
f (Left x) = Left x
f (Right _) = Right False
In the first case, it would be a shame to allocate a fresh Left
constructor just to change the type. So GHC has a low-level optimization (after the core-to-core optimization pipeline) that tries to turn this into (essentially)
f p@(Left x) = unsafeCoerce p
f (Right _) = Right False
That means that you could have m :: Either x a
and n :: Either x b
where m
and n
refer to the same heap object despite a
and b
having completely unrelated types. If you create a stable name for m
and a stable name for n
, then those stable names will compare equal! If you posit even as much as
sameSNCoercibleTypes
:: StableName a
-> StableName b
-> Maybe (Coercion a b)
then you can use m
and n
to "prove" Coercible (Either x a) (Either x b)
from which you can convert any a
into any b
. It's a bit delicate, but since it's possible at all, assuming otherwise is rather unsafe.
Upvotes: 4