如何解释(等式a)

6
我需要创建一个带有两个参数的函数,一个是 Int 类型,另一个是 [Int] 类型,该函数返回一个新的 [Int] 数组,其中排除了第一个参数的所有出现次数。
我可以很容易地使用列表推导式和列表递归来创建函数。但是,我需要使用以下这些参数:
deleteAll_list_comp :: Integer -> [Integer] -> [Integer]
deleteAll_list_rec :: (Integer -> Bool) -> [Integer] -> [Integer]

然而,我的任务要求的参数是

deleteAll_list_comp :: (Eq a) => a -> [a] -> [a]
deleteAll_list_rec :: (Eq a) => a -> [a] -> [a]

我不知道如何阅读这个语法。正如Google告诉我的那样,(Eq a)仅仅是告诉Haskell,a是一种可比较的类型。然而,我不理解这有什么意义,因为所有的Int都是自然可比较的。我该如何解释和使用这些参数来实现方法?我的意思是,这些参数到底是什么?

@groovy @pelotom

谢谢,这让我很清楚。我现在明白了,它只是要求两个参数而不是三个。然而,我仍然在运行这段代码时遇到了问题。

deleteAll_list_rec :: (Eq a) => a -> [a] -> [a]
delete_list_rec toDelete [] = []
delete_list_rec toDelete (a:as) =
        if(toDelete == a) then delete_list_rec toDelete as
        else a:(delete_list_rec toDelete as)

这让我出现了一个"The type signature for deleteAll_list_rec lacks an accompanying binding"的错误,这对我来说毫无意义,因为我已经正确地绑定了要求,不是吗?根据我的一点经验,(a:as)被视为一个列表,同时从中提取第一个元素。为什么会产生错误呢?
deleteAll_list_comp :: (Eq a) => a -> [a] -> [a]
deleteAll_list_comp toDelete ls = [x | x <- ls, toDelete==x]

不是吗?


2013年2月7日更新:对于那些未来可能遇到相同问题的人,我在这个链接中找到了一些关于Haskell的好信息,包括我的问题:http://learnyouahaskell.com/types-and-typeclasses

"有趣。我们在这里看到一个新事物,即 => 符号。=>符号之前的所有内容都被称为类约束。我们可以这样阅读先前的类型声明:等式函数接受任何两个相同类型的值,并返回一个布尔值。这两个值的类型必须是Eq类的成员(这是类约束)。

Eq类型类提供了一个测试相等性的接口。任何可以在该类型的两个值之间进行相等性测试的类型都应该是Eq类的成员。除了IO(用于处理输入和输出的类型)和函数之外,所有标准的Haskell类型都是Eq类型类的一部分。"


为什么不把你的代码发布出来,这样我们就可以看看你做了什么。 - Philip JF
1
你在类型签名中称其为 deleteAll_list_rec,但在实现中却称其为 delete_list_rec - Daniel Fischer
我已经发布了我的代码。Daniel 指出了我非常幼稚的错误 :) 非常感谢。 - Soulzityr
2个回答

20

参数的一种思考方式是:

(Eq a) => a -> [a] -> [a]

(Eq a) =>   means any a's in the function parameters should be members of the 
            class Eq, which can be evaluated as equal or unequal.*

a -> [a]    means the function will have two parameters: (1) an element of
            type a, and (2) a list of elements of the same type a (we know that 
            type a in this case should be a member of class Eq, such as Num or 
            String).

-> [a]      means the function will return a list of elements of the same 
            type a; and the assignment states that this returned list should 
            exclude any elements that equal the first function parameter, 
            toDelete.

(* edited based on pelotom's comment)


3
我赞成这个观点,因为我认为它提供了一个简洁的概括,但我会修改 "(Eq a) =>" 片段的描述。它并不是说 "函数只会接受 Eq 类的成员作为参数",而是说在函数签名的其余部分中,无论你在哪里看到类型参数 'a',你都可以确定它代表的是 Eq 的成员类型。 - Tom Crockett
谢谢Pelotom,这帮助我更好地理解了自己。 - גלעד ברקן
我不喜欢"a -> [a]表示函数将有两个参数...",更好的方式是采用a -> [a] ->。但这样做只会留下[a]作为结果,我同意使用-> [a]更好。唉 :( - Daniel Fischer
@groovy谢谢!你的评论帮了我很多。我已经有所进展并理解了它,但出现了错误。你能看一下并告诉我为什么错了吗?谢谢! - Soulzityr
此外,函数参数中的任何a都应该是类Eq的成员,这意味着这些a在某种程度上独立于类Eq,但实际上这些a是相同的:您可以为a选择一个类型,其中必须(而不是应该)存在一个“Eq”的实例。 - phant0m
我更喜欢使用“应该”而不是“必须”。我认真选择了那个词,因为我知道Haskell解释器可能会针对不符合预期的变量a生成错误。 - גלעד ברקן

6
你实现的函数(或者说你认为你实现的函数)只能用于整数列表,而作业要求你创建一个可以处理所有类型的列表的函数,只要它们是可比较相等的(这样你的函数也可以用于布尔值或字符串列表)。你可能不需要做太多改动:尝试从你的代码中删除显式的类型注释,并询问ghci关于它会从你的代码中推断出的类型(使用: l yourfile.hs,然后使用:t deleteAll_list_comp)。除非你使用算术运算或类似的操作,否则你很可能会发现你的函数已经适用于所有Eq a类型。
作为一个更简单的例子来解释这个概念:假设我们想写一个函数isequal来检查相等性(有点毫无意义,但嘿)。
isequal :: Integer -> Integer -> Bool
isequal a b = (a == b)

这是对isequal的一个很好的定义,但我手动添加的类型限制比必须要强得多。事实上,在没有手动类型签名的情况下,ghci会推断出:

Prelude> :t isequal
isequal :: Eq a => a -> a -> Bool

这告诉我们一个函数将适用于所有输入类型,只要它们有deriving Eq,这意味着它们上定义了适当的==关系。


然而,你的_rec函数仍存在问题,因为它应该与你的_comp函数执行相同的操作,类型签名应该匹配。


非常感谢,是的,我终于明白了参数建立的差异,并意识到我在第一次作业中也犯了这个错误。老师讲课不太清晰,在课堂上所有的例子都显示参数从a到更严格的原始类型。感谢您的澄清。然而,我仍然遇到了错误,很可能是由于我对Haskell的知识不足,如果您愿意看一下我的简单方法 :) - Soulzityr

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接