19

是否建议在 Haskell 中始终进行详尽的模式匹配,即使对于“不可能”的情况也是如此?

例如,在下面的代码中,我在 foldr 的“累加器”上进行模式匹配。我完全控制了累加器的内容,因为我创建了它(它不是作为输入传递给我的,而是在我的函数中构建的)。因此,我知道某些模式永远不应该匹配它。如果我力求永远不要出现“模式匹配不完全”错误,那么我会为其放置一个模式匹配,该错误只是带有“此模式不应该发生”的消息。很像 C# 中的断言。我想不出还有什么可做的。

在这种情况下,你会推荐什么做法,为什么?

这是代码:

gb_groupBy p input = foldr step [] input
   where
      step item acc = case acc of
           []                           -> [[item]]
           ((x:xs):ys)                  -> if p x item
                                           then (item:x:xs):ys
                                           else [item]:acc

不匹配的模式(由解释器报告)是:

警告:模式匹配并非详尽无遗在另一种情况下:模式不匹配:[]:_

4

6 回答 6

21

这可能更多的是风格问题。就个人而言,我会投入一个

_ -> error "Impossible! Empty list in step"

如果只是为了使警告静音:)

于 2009-05-07T05:23:25.777 回答
12

您可以通过执行以下操作解决此特殊情况下的警告:

gb_groupBy p input = foldr step [] input
   where
     step item acc = case acc of
        []                           -> [[item]]
        (xs:xss)                      -> if p (head xs) item
                                         then  (item:xs):xss
                                         else [item]:acc

然后模式匹配完成,累加器头部的空列表的“不可能”条件将导致运行时错误但没有警告。

另一种看待不完整模式匹配的更普遍问题的方法是将它们视为“代码气味”,即表明我们正在尝试以次优或非 Haskellish 方式解决问题,并尝试重写我们的函数。

使用 foldr 实现 groupBy 使得无法将其应用于无限列表,这是 Haskell List 函数试图在语义上合理的地方实现的设计目标。考虑

take 5 $ groupBy (==) someFunctionDerivingAnInfiniteList

如果前 5 组 wrt 相等是有限的,惰性求值将终止。这是您在严格评估的语言中无法做到的事情。即使您不使用无限列表,编写这样的函数也会在长列表上产生更好的性能,或者避免在评估表达式时发生的堆栈溢出

take 5 $ gb_groupBy (==) [1..1000000]

List.hs中, groupBy 是这样实现的:

groupBy         :: (a -> a -> Bool) -> [a] -> [[a]]
groupBy _  []       =  []
groupBy eq (x:xs)   =  (x:ys) : groupBy eq zs
                           where (ys,zs) = span (eq x) xs

这使解释器/编译器能够仅评估结果所需​​的计算部分。 span产生一对列表,其中第一个由列表头部的(连续)元素组成,都满足谓词,第二个是列表的其余部分。它也被实现为在无限列表上工作。

于 2009-05-07T10:34:31.970 回答
9

我发现对案例模式进行详尽检查是必不可少的。我尽量不要_在顶级案例中使用,因为_匹配所有内容,并且通过使用它会破坏详尽检查的价值。这对于列表不太重要,但对于用户定义的代数数据类型至关重要,因为我希望能够添加一个新的构造函数并让编译器对所有缺失的情况进行 barf。出于这个原因,我总是在-Werror打开的情况下编译,所以我没有办法遗漏一个案例。

正如所观察到的,您的代码可以使用这种情况进行扩展

[] : _ -> error "this can't happen"

在内部,GHC 有一个panic函数,它不像error会给出源坐标,但我查看了实现,无法确定它的头部或尾部。

于 2009-05-07T21:24:41.970 回答
4

为了跟进我之前的评论,我意识到有一种方法可以确认丢失的案例,但仍然会收到文件/行号的有用错误。这并不理想,因为它只会出现在未优化的版本中(请参阅此处)。

...
[]:xs -> assert False (error "unreachable because I know everything")
于 2009-05-07T14:04:51.873 回答
2

类型系统是你的朋友,警告是让你知道你的函数有裂缝。最好的方法是在类型之间寻求更干净、更优雅的配合。

考虑 ghc 的定义groupBy

groupBy                 :: (a -> a -> Bool) -> [a] -> [[a]]
groupBy _  []           =  []
groupBy eq (x:xs)       =  (x:ys) : groupBy eq zs
                           where (ys,zs) = span (eq x) xs
于 2010-01-27T18:11:17.463 回答
1

我的观点是,不可能的情况是undefined
如果它是未定义的,我们有一个函数来处理它:狡猾的命名undefined

用以下方式完成匹配:

_ -> undefined

你有它!

于 2014-04-22T10:56:24.683 回答