Coercible Bool Mark is not required. Mark-instances can be derived via Bool without it.
Generic types whose generic representations (Rep) are Coercible can be converted to each other:
from coerce to
A -----> Rep A () -----> Rep Via () -----> Via
For the datatype Mark this means instances (Eq, ..) can be derived via instances of Bool.
type Mark :: Type
data Mark = Nought | Cross
deriving
stock Generic
deriving Eq
via Bool <-> Mark
How does Bool <-> Mark work?
type (<->) :: Type -> Type -> Type
newtype via <-> a = Via a
First we capture the constraint that we can coerce between the generic representation of two types:
type CoercibleRep :: Type -> Type -> Constraint
type CoercibleRep via a = (Generic via, Generic a, Rep a () `Coercible` Rep via ())
Given this constraint we can move from a to it via type, creating intermediate Reps:
translateTo :: forall b a. CoercibleRep a b => a -> b
translateTo = from @a @() >>> coerce >>> to @b @()
Now we can easily write an Eq instance for this type, we assume an Eq via instance for the via type (Bool in our case)
instance (CoercibleRep via a, Eq via) => Eq (via <-> a) where
(==) :: (via <-> a) -> (via <-> a) -> Bool
Via a1 == Via a2 = translateTo @via a1 == translateTo @via a2
The instance for Semigroup requires translating via back to a
instance (CoercibleRep via a, Semigroup via) => Semigroup (via <-> a) where
(<>) :: (via <-> a) -> (via <-> a) -> (via <-> a)
Via a1 <> Via a2 = Via do
translateTo @a do
translateTo @via a1 <> translateTo @via a2
Now we can derive Eq and Semigroup!
-- >> V3 "a" "b" "c" <> V3 "!" "!" "!"
-- V3 "a!" "b!" "c!"
type V4 :: Type -> Type
data V4 a = V4 a a a a
deriving
stock Generic
deriving (Eq, Semigroup)
via (a, a, a, a) <-> V4 a
Using a newtype from the beginning avoids this boilerplate but once it's up it can be reused. It is simple to write a newtype and use pattern synonyms to cover it up.