我已经定义了一个类型
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如何获取==
的参数并遍历模式。它有可能吗?
答案 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时,你很少需要这样做。