鉴于来自TypeClassopedia的以下数据类型:
data Cons a = Cons a (Cons a) | Empty deriving (Show, Eq)
我实现了它的邪恶Functor实现:
instance Functor Cons where
fmap _ Empty = Empty
fmap f (Cons x xs) = Cons (f x) (Cons (f x) (fmap f xs) )
然后,我尝试编写一个函数(quickcheck属性),它接受Cons a
并返回Bool
:
prop_id_functor_law :: Cons a -> Bool
prop_id_functor_law x = fmap id x == id x
但是,我收到编译时错误:
Prelude> :l EvilFunctor
[1 of 1] Compiling EvilFunctor ( EvilFunctor.hs, interpreted )
EvilFunctor.hs:18:23:
No instance for (Eq a) arising from a use of `=='
Possible fix:
add (Eq a) to the context of
the type signature for prop_id :: Cons a -> Bool
In the expression: fmap id x == id x
In an equation for `prop_id': prop_id x = fmap id x == id x
Failed, modules loaded: none.
我粗略的直觉是这个编译时错误是有道理的。两个a
如何比较,除非他们实现了Eq
类型类?
但是,当我定义deriving ... Eq
时,data Cons a
甚至做了什么?
答案 0 :(得分:4)
它表示您需要将约束添加到prop_id_functor_law
,因此它是prop_id_functor_law :: Eq a => Cons a -> Bool
。 deriving
部分仅表示它派生实例
instance Eq a => Eq (Cons a) where
Empty == Empty = True
Cons a1 x1 == Cons a2 x2 = a1 == a2 && x1 == x2
_ == _ = False
您仍然需要约束type参数,以便满足Eq
实例。如果您要检查GHCi中的:info Cons
,您会看到该实例。
答案 1 :(得分:2)
deriving ... Eq
甚至在我定义数据Cons a
时做了什么?
当您派生实例时,GHC始终机械地生成一个实例,该实例实现类型结构的类型所需的逻辑。例如:
data Foo = Foo Int Int
deriving Eq
会给你类似的东西:
instance Eq Foo
where Foo a b == Foo a' b'
= a == a' && b == b'
但如果你改为:
data Foo a b = Foo a b
deriving Eq
然后它可以告诉它需要遵循相同的结构(如果两个包含的字段相等,则Foo
s相等)但是它被强制委托为类型a
和{{1部分比较。这些具有相等的类型并不总是这样,因此派生实例必须声明它们作为约束:
b
instance (Eq a, Eq b) => Eq (Foo a b)
where Foo a b == Foo a' b'
= a == a' && b == b'
也会发生同样的事情。因此,当值相等于时,两个Cons
值可用于等式。声明Cons a
这样的函数适用于所有prop_id_functor_law :: Cons a -> Bool
值,无论Cons a
是否成立,因此类型检查器不允许您在{{1}上调用Eq a
在实现中;它可能不支持相等性,并且保证您永远不会调用不受支持的操作是类型检查的重点。但是如果你改为==
,那么你可以使用Cons a
(责任转移到prop_id_functor_law :: Eq a => Cons a -> Bool
的来电者,以确保他们为支持的类型调用它平等)。