Why is GHC inferring unification from coercibility of associated data, and why is it contradicting its own checked type signature to do so?
The problem
{-# LANGUAGE ExplicitForAll #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TypeFamilies #-}
module Lib
(
) where
import Data.Coerce
class Foo a where
data Bar a
data Baz a = Baz
{ foo :: a
, bar :: Bar a
}
type BarSame a b = (Coercible (Bar a) (Bar b), Coercible (Bar b) (Bar a))
withBaz :: forall a b. BarSame a b => (a -> b) -> Baz a -> Baz b
withBaz f Baz{..} = Baz
{ foo = f foo
, bar = coerce bar
}
This is all well and good - GHC will happily compile this code, and is confident that withBaz
has the declared signature.
Now, let's try to use it!
instance (Foo a) => Foo (Maybe a) where
data Bar (Maybe a) = MabyeBar (Bar a)
toMaybeBaz :: Baz a -> Baz (Maybe a)
toMaybeBaz = withBaz Just
This gives an error - but a really weird one:
withBaz Just
^^^^^^^^^^^^
cannot construct the infinite type: a ~ Maybe a
Indeed, if I go into GHCi, and ask it to give me the type of withBaz
:
ghc>:t withBaz
withBaz :: (b -> b) -> Baz b -> Baz b
That's not the signature I gave it.
Coercibility
I suspect that GHC is treating the type arguments of withBaz
as though they have to unify, because it's inferring Coercible a b
from Coercible (Bar a) (Bar b)
. But because it's a data family, they don't even need to be Coercible
- certainly not unifiable.
Update!
The following change fixes the compilation:
instance (Foo a) => Foo (Maybe a) where
newtype Bar (Maybe a) = MabyeBar (Bar a)
That is - declare the data family as a newtype
, instead of a data
. This seems consistent with GHC's treatment of Coercible
in the language in general, in that
data Id a = Id a
will not cause a Coercible
instance to be generated - even though it should definitely be coercible to a
. With the above declaration, this will error:
wrapId :: a -> Id a
wrapId = coerce
But with a newtype
declaration:
newtype Id a = Id a
then the Coercible
instance exists, and wrapId
compiles.
test :: forall a b. (Coercible (Bar a) (Bar b)) => Bar a -> Bar b
with implementationtest = coerce
ends up with the typetest :: Bar b -> Bar b
in GHCi. That said, can you provide an example of usingwithBaz
at any actually concrete type? For instance, fortoMaybeBaz
, what type do you have in mind that is coercible toMabyeBar (Bar a)
? – CongratulantMabyeBar (Bar a)
?" -Bar a
, whichBar (Maybe a)
is a wrapper around. They clearly have the same representation in memory, so they should be coercible. – Pipsissewacoerce
in this way, and I found that it had anewtype
declaration for the associated data family, rather than adata
one. – Pipsissewa