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')
, andInstances 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 betweenBool
andMark
, what's stopping it from coercingNought
toTrue
andCross
toFalse
? It may be thatBool
andMark
are represented in memory the same way, but there is no guarantee that they are semantically similar enough to warrant aCoercible
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 ofgenericCoerce
from this other question