4

在 Haskell 中,我想编写一个递归函数,它对于给定的数字列表会更改子列表中每个元素的符号:

list = [[1, 3, 6.7, 7.0], [], [1, 8.22, 9, 0]]

multiply (x:xs) = [n * (-1) | n <- x] : multiply xs

但我收到一个错误:

[[-1.0,-3.0,-6.7,-7.0],[],[-1.0,-8.22,-9.0,-0.0]
*** Exception: learning.hs:26:1-48: 
    Non-exhaustive patterns in function multiply

谁能告诉我,如何处理空子列表的异常?

4

4 回答 4

7

实际上问题不是空的子列表

ghci> multiply [[1, 2, 3], [4, 5, 6]]
[[-1,-2,-3],[-4,-5,-6]*** Exception: <interactive>:2:1-50: Non-exhaustive patterns in function multiply

您使用 处理子列表[n * (-1) | n <-x],列表推导在空列表上工作正常;这个将找不到要乘以的元素-1,因此会产生一个空列表。您可以在引用的输出中看到空列表;之后它甚至会继续产生更多的输出,这很确定它在处理空子列表时没有抛出异常。

所以有什么问题?好吧,让我们看看错误消息实际上说的是什么:Non-exhaustive patterns in function multiply. 这意味着函数中的某处代码multiply正在执行模式匹配,而被匹配的值实际上并不适合您提供的任何模式。好吧,在你的整个函数中只有一个地方可以进行任何模式匹配,那就是multiply (x: xs), 所以这一定是问题所在!

现在列表总是空列表[]或表单item : rest_of_list(其中:是列表数据类型的另一个构造函数)。你:为表单提供了一个模式,所以multiply如果你将它应用到一个空列表肯定会抛出一个错误。这立即向我们展示了问题,即使没有将其与您的案例中实际发生的情况联系起来(我稍后会做)。

如何解决?您需要说明multiply []应该产生什么结果。通常,当您编写列表的递归函数时,您希望从这种形式开始:

func [] = _
func (x : xs) = _

然后填空。有时,有其他情况会很方便,[x]或者[x, y]如果您需要专门处理具有 1 个或 2 个元素的列表。只有极少数情况下您才应该忽略 的情况[],因为如果您这样做,您的函数肯定会抛出您在某些调用的问题中看到的异常。

在这种情况下,multiply只需将其 list-of-list 参数的所有子列表中的所有值取反,因此很容易看出它应该对空的子列表执行什么操作只需返回一个空列表。所以我们会有:

multiply [] = []
multiply (x: xs) = [n * (-1) | n <-x]: multiply xs

(如果您将它输入到 GHCi 而不是文件中,则需要使用多行模式来输入它; enter:{启动多行模式,然后输入定义的所有行,然后 enter:}给出所有你的代码行一次到编译器)


现在,还有一个更明显的问题。你没有打电话multiply [],你打电话multiply [[1,3,6.7,7.0],[],[1,8.22,9,0]]那么为什么它抱怨参数与空列表的模式不匹配呢?原始调用没有,但是每次您调用原始调用时,它都会multiply在较小的列表中调用自己!

它与[ [1,3,6.7,7.0], [], [1,8.22,9,0] ]模式匹配x : xs,结果为x = [1,3,6.7,7.0]and xs = [ [], [1,8.22,9,0] ]。然后它调用multiply xs.

因此,在第二次调用中,我们将匹配[ [], [1,8.22,9,0] ]pattern x : xs。在本次评测中,x = []xs = [ [1,8.22,9,0] ]。我们multiply xs再次调用这个版本的xs.

现在在第三次调用中,我们匹配[ [1,8.22,9,0] ]pattern x : xs。为了成功,我们必须同时找到 xxs。单个元素列表的尾部是空列表,所以x = [1,8.22,9,0]but xs = []。然后我们multply xs再次调用这个版本的xs,这就是问题所在。现在我们尝试匹配[]该模式x : xs,但我们不能,但也没有其他模式可以尝试,所以我们只得到Non-exhaustive patterns in function multiply.

于 2021-09-06T22:53:41.933 回答
3

您在处理递归步骤方面做得非常出色。但与任何编程语言一样,我们还需要一个基本案例来终止递归。具体来说,您需要为[]. 如果给定了[],你希望你的函数返回[],因为没有更多的工作要做。考虑

multiply :: Num a => [[a]] -> [[a]]
multiply [] = []
multiply (x : xs) = [n * (-1) | n <- x] : multiply xs

底线正是您已经编写的内容。您需要第二行来处理[]输入。第一行是类型签名,虽然不是严格要求,但在 Haskell 中是很好的设计,如果出现问题,会产生更好的错误消息。

于 2021-09-06T22:24:38.040 回答
2

您在列表的尾部进行递归,这意味着最终您将multiply xs使用xs空列表进行调用,并且由于您没有为空列表指定大小写,它将引发错误。

如果我们因此使用列表运行代码,[1,4]我们将调用 with multiply [1,4],这将进行递归调用multiply [4],然后将进行递归调用[],然后它找不到与空列表匹配的模式,因此出现错误. 因此,这意味着您的函数缺少基本情况。我们可以将其实现为:

multiply :: Num a => [[a]] -> [[a]]
multiply [] = []
multiply (x: xs) = [n * (-1) | n <-x] : multiply xs

然而,我们不需要使用递归,实际上我们可以使用map :: (a -> b) -> [a] -> [b]and来实现negate :: Num a => a -> a

multiply :: Num a => [[a]] -> [[a]]
multiply = map (map negate)

或者我们甚至可以将其进一步推广到任何Functor

multiply :: (Foldable f, Foldable g, Num a) => f (g a) -> f (g a)
multiply = fmap (fmap negate)
于 2021-09-07T06:43:56.420 回答
1

您可以将 map 与包装相同列表理解的 lambda 函数一起使用:

Prelude> list=[[1,3,6.7,7.0],[],[1,8.22,9,0]]
Prelude> map (\x -> [n * (-1) | n <-x]) list
[[-1.0,-3.0,-6.7,-7.0],[],[-1.0,-8.22,-9.0,-0.0]]
于 2021-09-06T23:28:19.107 回答