是否建议在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个组相等是有限的,则延迟评估将终止.这是您不能用严格评估的语言做的事情.即使您不使用无限列表,编写这样的函数也会在长列表上产生更好的性能,或者避免在评估表达式时出现的堆栈溢出
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产生一对列表,其中第一个由列表头部的(连续)元素组成,所有元素都满足谓词,第二个列表是列表的其余部分.它也被实现为在无限列表上工作.
我发现详尽无遗地检查案例模式是不可或缺的.我尝试永远不要_
在顶级案例中使用,因为_
匹配所有内容,并且通过使用它,你会耗尽详尽检查的价值.这对于列表来说不那么重要,但对于用户定义的代数数据类型来说非常重要,因为我希望能够添加一个新的构造函数,并在所有缺失的情况下使用编译器barf.出于这个原因,我总是-Werror
打开编译,所以我无法遗漏案例.
如上所述,您可以使用此案例扩展代码
[] : _ -> error "this can't happen"
在内部,GHC有一个panic
函数,它不同于error
给出源坐标,但是我查看了实现并且无法做出它的头部或尾部.