是否可以在Haskell函数中调试模式匹配?

时间:2012-03-30 19:42:50

标签: debugging haskell pattern-matching

我已经定义了一个类型

data Expr = 
    Const Double
    | Add Expr Expr
    | Sub Expr Expr

并将其声明为Eq类型类的实例:

instance Eq Expr where
    (Add (Const a1) (Const a2)) == Const b = a1+a2 == b
    (Add (Const a1) (Const a2)) == (Add (Const b1) (Const b2)) = a1+a2 == b1 + b2

当然,表达式Sub (Const 1) (Const 1) == Const 0的评估将失败。如何在运行时调试模式匹配过程以发现它失败?我想看看Haskell如何获取==的参数并遍历模式。它有可能吗?

3 个答案:

答案 0 :(得分:3)

编辑:提供问题的真实答案......

我发现查看匹配模式的最简单方法是添加trace语句,如下所示:

import Debug.Trace

instance Eq Expr where
    (Add (Const a1) (Const a2)) == Const b = trace "Expr Eq pat 1" $ a1+a2 == b
    (Add (Const a1) (Const a2)) == (Add (Const b1) (Const b2)) = trace "Expr Eq pat 2" $ a1+a2 == b1 + b2
    -- catch any unmatched patterns
    l == r = error $ "Expr Eq failed pattern match. \n l: " ++ show l ++ "\n r: " ++ show r

如果你没有包含一个最终语句来捕获任何其他不匹配的模式,你会得到一个运行时异常,但我发现看到你得到的数据更有用。然后通常很容易看出为什么它与以前的模式不匹配。

当然,您不希望将其留在生产代码中。我只根据需要插入痕迹,然后在完成后删除它们。您还可以使用CPP将其从生产版本中删除。

我还想说,我认为模式匹配是错误的方法。最终会出现模式数量的组合爆炸,这种模式会很快变得无法管理。例如,如果要为Float创建Expr实例,则需要更多基本构造函数。

相反,你可能有一个解释函数interpret :: Expr -> Double,或者至少可以写一个。然后你可以定义

instance Eq Expr where
  l == r = interpret l == interpret r

通过模式匹配,您实际上是在Eq实例中重写您的解释函数。如果你想创建一个Ord实例,你最终会重新编写解释函数。

答案 1 :(得分:2)

如果您希望获得有关匹配可能失败的一些示例,您可以查看QuickCheck。有一个关于生成和测试递归数据类型的manual(测试数据的大小)的示例,它们似乎完全符合您的需求。

虽然-Wall标志为您提供了一个不匹配的模式列表,但QuickCheck运行会为您提供输入数据的示例,这些输入数据会导致您的命题失败。 例如,如果我为Expr编写了一个生成器,并且我向quickCheck输入了一个命题prop_expr_eq :: Expr -> Bool来检查Expr是否等于它自己,我获得的非常快速Const 0.0作为非匹配输入的第一个示例。

import Test.QuickCheck
import Control.Monad

data Expr = 
    Const Double
    | Add Expr Expr
    | Sub Expr Expr
    deriving (Show)

instance Eq Expr where
    (Add (Const a1) (Const a2)) == Const b = a1+a2 == b
    (Add (Const a1) (Const a2)) == (Add (Const b1) (Const b2)) = a1+a2 == b1 + b2

instance Arbitrary Expr where
    arbitrary = sized expr'
      where
        expr' 0 = liftM Const arbitrary
        expr' n | n > 0 = 
            let subexpr = expr' (n `div` 2)
            in oneof [liftM Const arbitrary,
                      liftM2 Add subexpr subexpr,
                      liftM2 Sub subexpr subexpr]

prop_expr_eq :: Expr -> Bool
prop_expr_eq e = e == e

如您所见,运行测试会给您一个反例,以证明您的相等测试是错误的。我知道这可能有点过分,但如果你把事情写得好的话,那么你的优势就是你的代码可以查看任意属性,而不仅仅是模式匹配穷举。

*Main> quickCheck prop_expr_eq
*** Failed! Exception: 'test.hs:(11,5)-(12,81): Non-exhaustive patterns in function ==' (after 1 test):  
Const 0.0

PS:关于使用QuickCheck进行单元测试的另一个好读物在免费书real world haskell中。

答案 2 :(得分:1)

您可以将复杂模式分解为更简单的模式,并使用trace查看正在发生的事情。像这样:

instance Eq Expr where
    x1 == x2 | trace ("Top level: " ++ show (x, y1)) True,
               Add x11 x12 <- x1,
               trace ("First argument Add: " ++ show (x11, x12)) True,
               Const a1 <- x11,
               trace ("Matched first Const: " ++ show a1) True,
               Const a2 <- x12,
               trace ("Matched second Const: " ++ show a2) True,
               Const b <- x2
               trace ("Second argument Const: " ++ show b) True
             = a1+a2 == b

这有点绝望,但绝望的时候需要采取绝望的措施。 :) 当你习惯了Haskell时,你很少需要这样做。