2014-11-23 71 views
2

鑑於從TypeClassopedia以下數據類型:代數數據類型和平等

data Cons a = Cons a (Cons a) | Empty deriving (Show, Eq)

我實現了其罪惡的函子實現:

instance Functor Cons where 
    fmap _ Empty  = Empty 
    fmap f (Cons x xs) = Cons (f x) (Cons (f x) (fmap f xs)) 

於是,我試圖寫一個函數(快速檢查財產)需要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類型?

然而,當我定義data Cons a時,deriving ... Eq甚至可以做什麼?

回答

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 

你仍然有類型參數是爲了限制爲Eq實例得到滿足。如果您要在GHCi中檢查:info Cons,您會看到該實例。

2

what did the deriving ... Eq even do when I defined data 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小號如果兩個字段都相等,則它們是相等的),但強制將ab的類型委託給相等部分進行比較。這些並不總是平等類型,所以派生的實例有權宣佈,他們這樣做的制約:

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的值,無論是否爲Eq a,所以類型檢查器將不允許您在Cons a範圍內調用==執行內容;它可能不支持平等,並保證你永遠不會調用不受支持的操作是類型檢查的關鍵。但是,如果您改爲prop_id_functor_law :: Eq a => Cons a -> Bool,那麼您可以使用==(並且責任移動到調用者prop_id_functor_law以確保他們將它稱爲支持相等的類型)。