Coercible Bool Mark
不是必需的。可以通过
Bool
派生出
Mark
实例。
具有可变换性的
Generic
类型,其通用表示(
Rep
)是
Coercible
的,可以相互转换。
from coerce to
A -----> Rep A () -----> Rep Via () -----> Via
对于数据类型
Mark
,这意味着实例 (
Eq
, ..) 可以通过
Bool
的实例来派生。
type Mark :: Type
data Mark = Nought | Cross
deriving
stock Generic
deriving Eq
via Bool <-> Mark
Bool <-> Mark
是如何工作的?
type (<->) :: Type -> Type -> Type
newtype via <-> a = Via a
首先,我们捕捉到一个约束条件,即我们可以在两种类型的通用表示之间进行强制转换
:
type CoercibleRep :: Type -> Type -> Constraint
type CoercibleRep via a = (Generic via, Generic a, Rep a () `Coercible` Rep via ())
基于这个约束条件,我们可以通过类型从
a
移动到它,并创建中间的
Rep
。
translateTo :: forall b a. CoercibleRep a b => a -> b
translateTo = from @a @() >>> coerce >>> to @b @()
现在,我们可以轻松地为这种类型编写一个
Eq
实例,我们假设
via
类型(在我们的情况下是
Bool
)有一个
Eq via
实例。
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
< p >
Semigroup
的实例需要将
via
转换回
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
现在我们可以推导出
Eq
和
Semigroup
!
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
从一开始就使用 newtype
可以避免这种样板代码,但一旦使用它,就可以重复使用。编写一个新的类型并使用模式同义词来掩盖它非常简单。
unsafeCoerce
的函数,它可能有效,但不建议使用。获得Coercible
实例的唯一方式是使用unsafeCoerce
进行本地操作。case unsafeCoerce (Coercion @() @()) :: Coercible Mark Bool of Coercion -> ....
。除非存在迫切的性能需求,否则我不会真正建议使用它。 - dfeuer