For a simple example, say I want a type to represent tic-tac-toe marks:
data Mark = Nought | Cross
Which is the same as Bool
Prelude> :info Bool
data Bool = False | True -- Defined in ‘GHC.Types’
But there's no Coercible Bool Mark
between them, not even if I import GHC.Types
(I first thought maybe GHC needs Bool
's defining place to be visible), the only way to have this instance seems to be through newtype
.
Probably I could have defined newtype Mark = Mark Bool
and define Nought
and Cross
with bidirectional patterns, I wish there's something simpler than that.
Unfortunately, you're out of luck. As the documentation for Data.Coerce
explains, "one can pretend that the following three kinds of instances exist:"
Self-instances, as in instance Coercible a a
,
Instances for coercing between two versions of a data type that differ by representational or phantom type parameters, as in instance Coercible a a' => Coercible (Maybe a) (Maybe a')
, and
Instances between new types.
Furthermore, "Trying to manually declare an instance of Coercible
is an error", so that's all you get. There are no instances between arbitrarily different data types, even if they look similar.
This may seem frustratingly limiting, but consider this: if there were a Coercible
instance between Bool
and Mark
, what's stopping it from coercing Nought
to True
and Cross
to False
? It may be that Bool
and Mark
are represented in memory the same way, but there is no guarantee that they are semantically similar enough to warrant a Coercible
instance.
Your solution of using a newtype and pattern synonyms is a great, safe way to get around the problem, even if it is a little annoying.
Another option is to consider using Generic
. For instance, check out the idea of genericCoerce
from this other question