haskellderivingcoercederivingvia

Is it possible to establish Coercible instances between custom types and standard library ones?


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.


Solution

  • 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