【问题标题】:Haskell quickcheck to generate and test rose trees?Haskell quickcheck 生成和测试玫瑰树?
【发布时间】:2013-07-15 19:21:17
【问题描述】:

我正在尝试一个简单的玫瑰树代码。

data RoseT a = Leaf a | Node a [RoseT a] deriving (Show)

instance Eq (RoseT a) where
 (==) (Leaf a) (Leaf b) = a == b
 (==) (Node a rs1) (Node b rs2) = and ((a==b): (zipWith (==) rs1 rs2))
 (==) _ _ = False 

我可以使用 quickcheck 来测试 Eq 实例的实现吗?如果是,如何?如果没有,最好的选择是什么?

我还有一个函数可以执行以下操作:

appendPath :: (RoseT a) -> (RoseT (a,[a]))
appendPath rst = appendPath' [] rst 

appendPath :: [a] -> (RoseT a) -> (RoseT (a,[a]))
appendPath' p (Leaf a) = Leaf (a,p)
appendPath' p (Node a rs) = Node (a,p) (map (appendPath' (a:p)) rs)

函数 appendPath 将玫瑰树作为输入,并返回一个玫瑰树,其中包含树的每个节点中从节点到根的路径。我在另一个函数中使用此信息。

如何使用 quickcheck 在大型玫瑰树上检查此实现?

编辑: 正如 mhwombat 所建议的那样,编写将类型参数作为参数的生成器似乎是不可能的。 所以这是我的类型参数。 我想构造一个代表有效随机算术表达式的字符串RoseT,其中算术表达式本身遵循以下结构:

data Expr = Var String | AddExpr [Expr] | MulExpr [Expr] deriving Show

那么,有没有办法生成随机 RoseT Expr,其中 Expr 本身是仅使用快速检查随机生成的?

再次感谢 mhwombat,感谢您陪伴我的婴儿步。

【问题讨论】:

  • 你想测试什么?当然可以使用 quickcheck 来测试关于你的树的属性,但你有什么想法?
  • 您的RoseT 定义似乎不正确。玫瑰树的 ADT 中不应该有 Leaf 模式,因为根据该定义,叶节点是否应该写为 Leaf 42Node 42 [] 是模棱两可的。这是模棱两可的,因为这两个表达式应该相等,但它们不是。

标签: haskell tree quickcheck


【解决方案1】:

除非我遗漏了什么,否则您对RoseTEq 实现与默认派生实现相同。所以你可以说

data RoseT a = Leaf a | Node a [RoseT a] deriving (Show, Eq)

忘记instance Eq (RoseT a) where 的东西。

下一个问题是这是否能满足您的测试需求。如果您使用浮点类型参数进行测试,例如,RoseT Double,那么您需要考虑由于舍入导致的差异。在这种情况下,您需要一个比较两棵树并查看值是否“足够接近”的函数。

但是,我怀疑您的 RoseT 实现不会以任何方式依赖于类型参数。在这种情况下,您可以使用像CharInt 这样的简单类型对其进行测试,然后使用== 进行您需要的任何比较。

appendPath 有两个类型签名。我认为第二个应该是appendPath'

appendPath' :: [a] -> (RoseT a) -> (RoseT (a,[a]))

现在了解如何测试它。如果您/QuickCheck 对正在测试的树的复杂性有一些控制,那将是最好的。这将对您有所帮助,因为将首先测试最简单的树,因此您会“尽早”发现错误(即,使用更简单的测试用例更容易调试)。你可以通过为你的类实现一个“大小”的生成器来做到这一点。这是一种方法。 “size”参数的值越高,树的级别可能越多。

type TestRoseT = RoseT Char

sizedArbTestRoseT :: Int -> Gen TestRoseT
sizedArbTestRoseT 0 = do
  c <- arbitrary
  return $ Leaf c
sizedArbTestRoseT n = do
  c <- arbitrary
  subtreeCount <- choose (0,n-1)
  subtrees <- vectorOf subtreeCount (sizedArbTestRoseT (n-1))
  return $ Node c subtrees

instance Arbitrary TestRoseT where
  arbitrary = sized sizedArbTestRoseT

prop_appendPath_does_something :: TestRoseT -> Property
prop_appendPath_does_something t = undefined -- stub

我们可以对生成的测试数据进行采样,如下所示:

λ> sample (sizedArbTestRoseT 2)
Node '\a' [Node '\RS' []]
Node '?' []
Node '\158' []
Node 'o' [Node 'E' []]
Node '\196' []
Node '4' [Node 'G' []]
Node ';' [Node 'f' []]
Node 'A' [Node '\CAN' []]
Node '!' []
Node 'q' [Node '\t' []]
Node '\'' [Node '\212' []]

编辑:

对于您的Expr 类型,我们可以编写一个生成器,如下所示:

sizedArbExpr :: Int -> Gen Expr
sizedArbExpr 0 = do
  s <- arbitrary
  return $ Var s
sizedArbExpr n = do
  es <- vectorOf 2 (sizedArbExpr (n-1))
  elements [AddExpr es, MulExpr es]

instance Arbitrary Expr where
  arbitrary = sized sizedArbExpr

我们需要修改TestRoseT 及其生成器,使树的复杂度与“大小”参数一致:

type TestRoseT = RoseT Expr

sizedArbTestRoseT :: Int -> Gen TestRoseT
sizedArbTestRoseT 0 = do
  c <- sizedArbExpr 0 -- changed this to keep complexity in bounds
  return $ Leaf c
sizedArbTestRoseT n = do
  c <- sizedArbExpr (n-1) -- changed this to keep complexity in bounds
  subtreeCount <- choose (0,n-1)
  subtrees <- vectorOf subtreeCount (sizedArbTestRoseT (n-1))
  return $ Node c subtrees

测试这些修改会得到类似的结果:

λ> sample (sizedArbTestRoseT 3)
Node (MulExpr [MulExpr [Var "",Var ""],AddExpr [Var "",Var ""]]) [Node (MulExpr [Var "",Var ""]) [Node (Var "") []]]
Node (MulExpr [AddExpr [Var "",Var ""],AddExpr [Var "",Var ""]]) [Node (AddExpr [Var "",Var ""]) []]
Node (MulExpr [AddExpr [Var "\164D",Var "\151\246\FS"],MulExpr [Var ":\149j\EM",Var "h\253\255"]]) [Node (MulExpr [Var "\CAN\a\ACK",Var "\184"]) [Node (Var "t\154]\\") []],Node (MulExpr [Var "\135",Var "\f\DEL\\"]) [Node (Var "\SOH\DEL") []]]
Node (AddExpr [AddExpr [Var "",Var ""],MulExpr [Var "Kj\STXV",Var "D\141<s\187"]]) []
Node (AddExpr [MulExpr [Var "\252",Var "`"],MulExpr [Var "\167`t\196",Var ":\135\NULdr\237\167"]]) []
Node (AddExpr [MulExpr [Var "]\173\&28D\SOCom",Var "^\196\ETB2\216\&2\GS\ENQ\ENQ"],AddExpr [Var "$bB\212\SOH\146\234",Var "\DC3\213\&3\SUB\\}^\246(\200"]]) [Node (MulExpr [Var "l;\133\EM\147#\SUBN\\\t",Var "\235\151U\129m3|"]) [Node (Var "\NULb\133") []],Node (AddExpr [Var "\187\EOT\165S\207\r\"\RS",Var "4"]) []]
Node (MulExpr [MulExpr [Var "%0eK",Var "`N**k\FS6\NAK"],MulExpr [Var "'lUL\NAKRPc\ENQR",Var "j\232+`\FS@n"]]) [Node (AddExpr [Var "H\DC1C%\DC48<\t\ETX.L",Var "\235+\v\STXX,\NAK\SUBQc="]) [Node (Var "f\254oX?w\224\195~/") []]]
Node (AddExpr [AddExpr [Var "P",Var "\148G\STX\DEL*\136(\161\159\&7"],AddExpr [Var "\218\136-",Var "9?\128\159\b\b%3t}\131qe"]]) [Node (MulExpr [Var "\198\249\&4\176\193/}\DC28",Var ")Gl0ym\GS"]) [Node (Var ")\204\226qA\175") []]]
Node (MulExpr [MulExpr [Var "\t\186r.",Var "j\ENQ\183\NUL\NAK\129:rg[\170o\157g\238"],AddExpr [Var "\218F\226\248\156\225\&1",Var "vu\SOH\138+CKW\EM\167\&1n"]]) [Node (MulExpr [Var ",\241\158={o\182\"5\t\STX\ETX\DC2\218\162",Var "\197\&1"]) [Node (Var "u?a};\238") []]]
Node (MulExpr [MulExpr [Var "*",Var "R"],AddExpr [Var "\CAN8C",Var "\232V.\172ILy\162a"]]) []
Node (MulExpr [MulExpr [Var "\SI\240NF\249-\v$",Var "K`\STX\231w{"],MulExpr [Var "\DC1\255\209",Var "/\227\146\236\STX\185T3r\f"]]) [Node (MulExpr [Var "\229,\DLE\NAKwf[7P\160\DEL",Var "\134#\RS\SI0KCg\195\NAK\"\191\&6\243\193\SI"]) [Node (Var "\226\&7b8\f\EOTgF\171\GS}\189c\SUBc\ETX") []]]

顺便说一句,您说“编写将类型参数作为参数的生成器似乎是不可能的”。实际上可以这样做,但我认为这不是你真正想要的。

顺便说一句,您想要创建一个叶子包含二叉树 (Expr) 的树 (RoseT) 似乎有点不寻常。换句话说,您正在创建树的树。当然,我不知道你的申请。

【讨论】:

  • 谢谢。是的,我的RoseT impl 不太依赖于类型参数。但是RoseT 的后续使用确实如此。像幻影类型的东西。例如,我想用它来处理具有某些属性的字符串,比如作为有效算术表达式的字符串。我已编辑问题以添加此信息。
  • 我面临着类似的问题。类型参数(在树 a 中)并不重要,但我想用 QuickCheck 测试的函数在其签名中具有类型参数,我无法将这些更改为引用某些 TestRoseT 类型。是否可以更改生成器以便使用字符树来测试我的功能?
  • @RichAshworth 通常,当您有一个带有类型参数的类型(例如,Tree a)时,您可能只需要使用一种类型对其进行测试。例如,一旦您测试了Tree Char,您就会知道它适用于Tree Int。所以和上面的例子类似,你可以定义type TestTree = Tree Char,为TestTrees写一个生成器,写属性来测试。原始函数仍将具有Tree a 的类型签名。唯一会用到TestTree 的地方是你的测试代码。如果此解释不清楚,您可能需要打开一个新问题,并链接到该问题。
  • 谢谢@mhwombat 我认为这是有道理的。您能否给出 prop_appendPath_does_something 函数的示例实现?我定义的属性只是属性类型...
  • @RichAshworth 假设你有 addNodecontainsNode 函数在你的树上运行。然后你可以定义prop_addNode_works n t = property $ (addNode t n) 'containsNode' n,它验证你添加一个节点后,它实际上在树中。
猜你喜欢
  • 1970-01-01
  • 2022-01-21
  • 1970-01-01
  • 2012-06-07
  • 1970-01-01
  • 1970-01-01
  • 2015-10-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多