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

StackOverflow https://stackoverflow.com/questions/832992

是否建议在 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

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

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

有帮助吗?

解决方案

这可能更像是风格问题而不是其他任何问题。就个人而言,我会加入

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

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

其他提示

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

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 组 w.r.t.等式是有限的,惰性求值将终止。这是用严格评估的语言无法做到的。即使您不使用无限列表,编写这样的函数也会在长列表上产生更好的性能,或者避免在计算诸如此类的表达式时发生堆栈溢出

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

列表.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

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

我发现详尽无遗地检查案件模式是不可或缺的。我尝试永远不要在顶层的情况下使用 _ ,因为 _ 匹配所有内容,并且通过使用它可以消除穷举检查的价值。这对于列表来说不那么重要,但对于用户定义的代数数据类型来说非常重要,因为我希望能够添加新的构造函数并在所有缺失的情况下使用编译器barf。出于这个原因,我总是在 -Werror 打开的情况下编译,所以我无法遗漏案例。

如上所述,您可以使用此案例扩展您的代码

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

在内部,GHC有一个 panic 函数,与 error 不同,它会给出源坐标,但是我查看了实现并且无法做出它的头或尾。

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

...
[]:xs -> assert False (error "unreachable because I know everything")

类型系统是你的朋友,警告让你知道你的功能有裂缝。最好的方法是在类型之间寻求更清晰,更优雅的配合。

考虑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

我的观点是,一个不可能的案例是 undefined
如果它未定义,我们有一个函数:狡猾地命名为 undefined

完成与以下内容的匹配:

_ -> undefined

你有它!

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top