首页 文章

是否建议在Haskell中始终使用详尽的模式匹配,即使是“不可能”的情况?

提问于
浏览
17

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

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

警告:模式匹配是非详尽的在案例替代方案中:模式不匹配:[]:_

6 回答

  • 2

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

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

    如果只是沉默警告:)

  • 20

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

    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]
    

    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产生一对列表,其中第一个由列表头部的(连续)元素组成,所有元素都满足谓词,第二个列表是列表的其余部分 . 它也被实现为在无限列表上工作 .

  • 9

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

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

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

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

  • 11

    为了跟进我之前的评论,我意识到有一种方法可以确认丢失的情况,但仍然会得到文件/行号的有用错误 . 它只会出现在未经优化的构建中(参见here) .

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

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

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

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

    完成与以下类似的匹配:

    _ -> undefined
    

    你有它!

相关问题