【问题标题】:Why do non-exhaustive guards cause irrefutable pattern match to fail?为什么非详尽的守卫会导致无可辩驳的模式匹配失败?
【发布时间】:2011-10-29 20:55:46
【问题描述】:

我在 Haskell 中有这个功能:

test :: (Eq a) => a -> a -> Maybe a
test a b
  | a == b = Just a
test _ _ = Nothing

这是我尝试使用不同输入的函数时得到的结果:

ghci>test 3 4
Nothing
ghci>test 3 3
Just 3

根据 Real World Haskell 的说法,第一个模式是无可辩驳的。但似乎test 3 4 没有失败第一个模式,而是匹配第二个。我预计会出现某种错误——也许是“非详尽的守卫”。那么这里到底发生了什么,有没有办法在这种意外发生时启用编译器警告?

【问题讨论】:

    标签: haskell pattern-matching guard


    【解决方案1】:

    第一个模式确实是一个“无可辩驳的模式”,但这并不意味着它总是会选择你函数的相应右侧。它仍然受制于可能会像您的示例中那样失败的守卫。

    为确保涵盖所有情况,通常使用otherwise 来设置一个始终成功的最终保护。

    test :: (Eq a) => a -> a -> Maybe a
    test a b
      | a == b    = Just a
      | otherwise = Nothing
    

    请注意,otherwise 并没有什么神奇之处。它在 Prelude 中定义为otherwise = True。但是,在最后一种情况下使用 otherwise 是惯用的。

    在一般情况下,让编译器警告非详尽的保护是不可能的,因为它涉及解决停机问题,但是存在像 Catch 这样的工具,它们试图在确定是否比编译器做得更好所有情况都包括或不包括在普通情况下。

    【讨论】:

    • 那么如果它是一个无可辩驳的模式,它怎么会不匹配呢?匹配是否取决于守卫的成功?是不是先匹配,守卫失败后不匹配?
    • @Matt:该模式确实匹配,并且任何被它绑定的变量都可以用于警卫,然后可能会失败。发生这种情况时,其余的守卫会按顺序进行审判。如果它们都失败了,则尝试下一个模式。如果没有更多模式可供尝试,则会出现非详尽的模式匹配错误。
    • 在 GHC 中 otherwise 特殊的。如果您尝试自己定义它,您最终会收到非详尽的匹配编译时警告(当然,前提是您启用了这些警告)。
    【解决方案2】:

    如果你遗漏了第二个子句,编译器应该会警告你,也就是说,如果你的最后一个匹配有一组保护,而最后一个不是真的。

    一般来说,测试守卫的完整性显然是不可能的,因为它和解决停机问题一样困难。

    回答马特的评论:

    看例子:

    foo a b 
       | a <= b = True
       | a >  b = False
    

    人类可以看到两个守卫中的一个必须是真实的。但是编译器不知道a &lt;= ba &gt; b

    现在寻找另一个例子:

    fermat a b c n 
        | a^n + b^n /= c^n = ....
        | n < 0 = undefined
        | n < 3 = ....
    

    为了证明这组守卫是完备的,编译器必须证明费马大定理。在编译器中不可能做到这一点。请记住,守卫的数量和复杂性不受限制。编译器必须是数学问题的通用求解器,这些问题在 Haskell 本身中已经说明。

    更正式地说,在最简单的情况下:

     f x | p x = y
    

    编译器必须证明如果p x 不是底部,那么对于所有可能的x,p xTrue。换句话说,无论x 是什么或评估为True,它都必须证明p x 是底部(不会停止)。

    【讨论】:

    • 你能解释一下为什么不可能吗?我对停机问题很熟悉,但不明白为什么这显然是不可能的。
    • 我喜欢使用Fermat's Last Theorem 的例子,对于初学者来说,这是数学中最著名的问题之一,直到 1995 年才被证明(尽管费马声称有一个太大的证明以适合边距)。
    • 在存在类型类的情况下,情况更糟。在您的第一个示例中,编译器必须证明a &lt;= ba &gt; b 甚至没有&lt;=&gt; 的实现可用! (事实上​​,证明这一点显然是不可能的,因为面对写得不好的实例,它甚至不一定是真的。)
    • @Daniel - 非常正确。尽管有人可能会争辩说,如果有人可以证明或反驳像 forall x.p x || 这样的定理q x,那么通过实例必须遵循的法律来丰富 cals 定义(例如 forall a b)也是可能的,也是一个很好的功能。 a = b && not (b > a)
    • It's impossible do do that in a compiler. 我敢肯定这不是/不可能/可以这么说。只是不实用。
    【解决方案3】:

    守卫并非无可辩驳。但是添加一个捕捉其他情况的最后一个守卫是非常常见(也是很好)的做法,因此您的函数变为:

    test :: (Eq a) => a -> a -> Maybe a
    test a b
      | a == b = Just a
      | True = Nothing
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-01-18
      • 1970-01-01
      • 1970-01-01
      • 2019-03-01
      • 1970-01-01
      • 2015-08-15
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多