如何在没有GADT或数据类型上下文的情况下定义List的Eq实例

时间:2015-05-29 02:38:17

标签: list haskell compiler-flags algebraic-data-types gadt

我正在使用Glasgow Haskell Compiler, Version 7.8.3, stage 2 booted by GHC version 7.6.3

我试图在Haskell中对List类型使用以下数据定义:

data Eq a => List a = Nil | Cons a (List a)

但是,默认情况下,-XDatatypeContexts标志是必需的,已删除,甚至已从语言中删除。它被广泛视为语言的错误。我也不想为List的定义使用特殊标志,因为我试图复制现有列表类型的功能。 然后我可以使用以下代码段:

data List a where
 Nil :: List a
 Cons :: Eq a => a -> List a -> List a

运行正常。这个解决方案的明显问题是现在我需要使用-XGADTs标志,在这种情况下我仍然不想依赖它,因为内置版本的列表不需要运行。有没有办法将Cons中的类型限制为Eq a,这样我就可以比较两个列表而不需要编译器标记而不使用derived关键字? 其余代码如下:

instance Eq (List a) where
 (Cons a b) == (Cons c d) = (a == c) && (b == d)
 Nil == Nil = True
 _ == _ = False
testfunction = Nil :: List Int
main = print (if testfunction == Nil then "printed" else "not printed")

我看到以下解决方案有效:

data List a = Nil | Cons a (List a)
instance Eq a => Eq (List a) where
 (Cons a b) == (Cons c d) = (a == c) && (b == d)
 Nil == Nil = True
 _ == _ = False
testfunction = Nil :: List Int
main = print (if testfunction == Nil then "printed" else "not printed")

但是,出于某种原因,它不适用于Eq的手动定义(此处为等于)。

class Equal a where  
 (=+=) :: a -> a -> Bool  
 (/+=) :: a -> a -> Bool  
 x =+= y = not (x /+= y)  
 x /+= y = not (x =+= y)
data List a = Nil | Cons a (List a)
instance Equal a => Equal (List a) where
 (Cons a b) =+= (Cons c d) = (a =+= c) && (b =+= d)
 Nil =+= Nil = True
 _ =+= _ = False
testfunction = Nil :: List Int
main = print (if testfunction =+= Nil then "printed" else "not printed")

我收到以下错误:

No instance for (Equal Int) arising from a use of ‘=+=’
    In the expression: testfunction =+= Nil
    In the first argument of ‘print’, namely
      ‘(if testfunction =+= Nil then "printed" else "not printed")’
    In the expression:
      print (if testfunction =+= Nil then "printed" else "not printed")

然而,通过使用GADT,我可以证明我的Equal类确实起作用。此代码有效:

class Equal a where  
 (=+=) :: a -> a -> Bool  
 (/+=) :: a -> a -> Bool  
 x =+= y = not (x /+= y)  
 x /+= y = not (x =+= y)
data List a where
 Nil :: List a
 Cons :: Equal a => a -> List a -> List a
instance Equal (List a) where
 (Cons a b) =+= (Cons c d) = (a =+= c) && (b =+= d)
 Nil =+= Nil = True
 _ =+= _ = False
testfunction = Nil :: List Int
main = print (if testfunction =+= Nil then "printed" else "not printed")

但是,我必须使用instance Equal (List a) where而不是instance Equal a => Equal (List a) where,否则我会收到错误:

No instance for (Equal Int) arising from a use of ‘=+=’
    In the expression: testfunction =+= Nil
    In the first argument of ‘print’, namely
      ‘(if testfunction =+= Nil then "printed" else "not printed")’
    In the expression:
      print (if testfunction =+= Nil then "printed" else "not printed")

2 个答案:

答案 0 :(得分:7)

您似乎正在尝试将列表限制为仅能够保存实施Eq的值,并且您无法在没有扩展的情况下执行此操作。但是,当List a具有实现a的类型时,您可以告诉编译器如何比较两个Eq。有两种简单的方法可以做到这一点。最简单的是使用派生语句:

data List a = Nil | Cons a (List a) deriving (Eq)

或者你可以手动定义它:

instance Eq a => Eq (List a) where
    (Cons a b) == (Const c d) = (a == c) && (b == d)
    Nil == Nil = True
    _ == _ = False

现在,只要您使用List的内容填充Eq类型,该列表也可以使用==进行比较。无需限制Cons内的值。你当然可以有一个正常的功能列表,比如

fs1 :: [Int -> Int]
fs1 = [(+1), (*3), (+2), (*4)]

或者在你的情况下

fs2 :: List (Int -> Int)
fs2 = Cons (+1) $ Cons (*3) $ Cons (+2) $ Cons (*4) Nil

可以用作

> map ($ 10) fs1
[11, 30, 12, 40]

并且给出了

map' :: (a -> b) -> List a -> List b
map' f Nil = Nil
map' f (Cons x xs) = Cons (f x) (map' f xs)

然后

> map' ($ 10) fs2
Cons 11 (Cons 30 (Cons 12 (Cons 40 Nil)))

虽然要在GHCi中实际查看它,但您还应该派生Show

data List a = Nil | Cons a (List a) deriving (Eq, Show)

还有一些其他有用的类型类也可以在GHC中派生出来。

要使其适用于您的自定义Equal类型类,您必须手动编写多个实例:

class Equal a where
    (=+=) :: a -> a -> Bool
    (/+=) :: a -> a -> Bool
    x =+= y = not (x /+= y)
    x /+= y = not (x =+= y)

instance Equal Int where
    x =+= y = x == y

instance Equal a => Equal (List a) where
    (Cons a b) =+= (Cons c d) = (a =+= c) && (b =+= d)
    Nil =+= Nil = True
    _ =+= _ = False

现在因为你有一个实例Equal IntEqual a => Equal (List a),你可以比较两个List Int

> let x = Cons 1 (Cons 2 (Cons 3 Nil)) :: List Int
> let y = Cons 1 (Cons 2 (Cons 3 Nil)) :: List Int
> x =+= y
True
> x =+= Nil
False

对于您要在List中存储并使用=+=的任何类型,您必须为该类型实施Equal

答案 1 :(得分:3)

通常的解决方案是使用这种结构:




 数据列表a = Nil |缺点(列表a)

实例Eq a => Eq(列出一个)其中
 (Cons a b)==(Cons c d)=(a == c)&& (b == d)
无== Nil = True
 _ == _ = False
  




注意到我已将 Eq a 添加为的约束实例,而不是数据类型。这样,您可以比较您尝试编写它的方式可以比较的所有列表是否相等,但是您也允许存在无法进行相等性比较的事物列表。你会遇到的每个Haskell版本都支持这个版本,即使是非常旧的版本,也没有扩展名。





当你想要添加一个<代码>显示实例, Ord 实例等;通过添加更多约束(可能会破坏现有代码,因为它不需要这些实例),你必须继续前进并使数据结构更具限制性。然而,如果您保持数据类型不受约束,只需使您的实例 Show a =&gt;显示(列表a) Ord a =&gt; Ord(列出一个)等,然后你可以显示支持两者的类型的顺序列表,但你仍然可以拥有(并显示)支持显示但不是 Ord ,反之亦然。

&#xA;&#xA;

另外,参数化类型有很多有用的类型类(例如列出要求它们在类型参数中完全通用。例如 Functor Applicative Monad ;对于您尝试创建的约束列表类型,无法正确实现这些。

&#xA;&#xA;

虽然 可以创建约束列表就像你想要的那样(但只是通过使用扩展,正如你所发现的那样),已经发现通常更有用的是让你的数据类型在它们的类型参数中完全通用,并且如果你的类型的特定用法我们需要对您在该使用网站上强加的类型参数施加限制,而不是 每次使用数据类型。这应该是你的“默认设置”;当你有充分的理由离开它(你可能在这里有这样的理由,但你没有在问题中给我们,所以没有人可以推荐最好的处理方式)。

&# XA;