List 如何在没有GADT或数据类型上下文的情况下定义列表的Eq实例
我使用的是由GHC 7.6.3版启动的List 如何在没有GADT或数据类型上下文的情况下定义列表的Eq实例,list,haskell,compiler-flags,algebraic-data-types,gadt,List,Haskell,Compiler Flags,Algebraic Data Types,Gadt,我使用的是由GHC 7.6.3版启动的Glasgow Haskell编译器,版本7.8.3,阶段2 我尝试对Haskell中的列表类型使用以下数据定义: data Eq a => List a = Nil | Cons a (List a) 但是,默认情况下,-XDatatypeContexts标志是必需的、无摩擦的,甚至从语言中删除。人们普遍认为这是一种语言的错误。我也不想对列表的定义使用特殊标志,因为我正在尝试复制现有列表类型的功能。 然后我可以使用以下代码段: data List
Glasgow Haskell编译器,版本7.8.3,阶段2
我尝试对Haskell中的列表类型使用以下数据定义:
data Eq a => List a = Nil | Cons a (List a)
但是,默认情况下,-XDatatypeContexts
标志是必需的、无摩擦的,甚至从语言中删除。人们普遍认为这是一种语言的错误。我也不想对列表的定义使用特殊标志,因为我正在尝试复制现有列表类型的功能。
然后我可以使用以下代码段:
data List a where
Nil :: List a
Cons :: Eq a => a -> List a -> List a
它运行良好。这个解决方案的一个明显问题是,现在我需要使用-XGADTs
标志,在这种情况下,我仍然不想依赖它,因为list的内置版本不需要运行。有没有办法将Cons
中的类型限制为Eq a
,这样我就可以比较两个列表,而不需要编译器标志,也不需要使用派生的
关键字?
其余代码如下:
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的手动定义(此处等于)
我得到以下错误:
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")
看起来您试图将列表限制为只能保存实现
Eq
的值,如果没有扩展,您就无法做到这一点。但是,当a
具有实现Eq
的类型时,您可以告诉编译器如何比较两个列表a
。有两种简单的方法可以做到这一点。最简单的是使用派生语句:
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
现在,每当您用实现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)
然后
虽然要在GHCi中实际查看它,您还应该派生Show
:
data List a = Nil | Cons a (List a) deriving (Eq, Show)
在GHC中还可以派生其他几个有用的类型类
要使其与自定义的
Equal
typeclass一起工作,您必须手动编写多个实例:
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 Int
和Equal a=>Equal(List a)
,所以您可以比较两个List Int
s:
> 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
对于要存储在
列表中并在上使用=+=
的任何类型,都必须为该类型实现Equal
。通常的解决方案是使用此结构:
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
注意,我添加了Eq a
作为实例的约束,而不是数据类型的约束。通过这种方式,你可以对所有列表进行平等性比较,就像你试图写它的方式一样,可以对所有列表进行平等性比较,但是你也允许存在无法对平等性进行比较的事物列表。您将遇到的每个Haskell版本都支持这一点,即使是非常旧的版本,也没有扩展
当您想要添加一个Show
实例、一个Ord
实例等时,这种方法也可以很好地扩展;要按您的方式执行,您必须继续返回,并通过添加更多约束(可能会破坏运行良好的现有代码,因为它不需要这些实例),使数据结构更具限制性。然而,如果您不限制数据类型,只让实例Show a=>Show(List a)
,Ord a=>Ord(List a)
等,那么您可以显示和排序恰好支持这两种类型的列表,但您仍然可以拥有(和Show)支持显示但不支持Ord
的类型列表,反之亦然
另外,对于参数化类型(如列表a
),有许多有用的类型类要求它们在其类型参数中是完全泛型的。例如函子
,应用程序
,单子
;无法为您尝试创建的受约束列表类型正确实现这些
虽然可以像您正在尝试的那样创建受约束的列表(但只能通过使用扩展,正如您所发现的那样),但人们发现,在数据类型的类型参数中保留完全通用的数据类型通常更为有用,如果您的类型的特定用法需要对类型参数施加限制,则可以在该用法站点上施加限制,而不是在每次使用该数据类型时施加限制。这应该是您的“默认设置”;当你有充分的理由时就离开它(你在这里很可能有这样的理由,但你在问题中没有告诉我们,因此没有人能推荐最好的处理方法)。似乎只要我实际使用内置的Eq类,这个解决方案就行得通。奇怪的是,一个定制的Eq类,如果不这样做的话,就不能使用这个解决方案。我定义了一个:class=a,其中(=+=)::a->a->Bool(+/+=)::a->a->Bool x=+=y=not(x/+=y)x/+=y=not(x=+=y)@TimothySwan,内置的Eq
类没有什么特别之处,只是它有一堆为内置类型定义的实例。你应该能够完全模仿它,包括这个解决方案(这是你问题的标准解决方案,我已经做了很多次)目前,你的答案似乎只适用于Haskell内置的Eq类。但是,我需要一个适用于任何类的解决方案。我正在相应地更新这个问题。@TimothySwan No.导出的p
> 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
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