给定一棵任意树,我可以在该树上构建一个子类型关系,使用舒伯特编号:
constructH :: Tree a -> Tree (Type a)
whereType
嵌套原始标签,并额外提供执行子/父(或子类型)检查所需的数据。使用舒伯特编号,两个 Int 参数就足够了。
data Type a where !Int -> !Int -> a -> Type a
这导致二元谓词
subtypeOf :: Type a -> Type a -> Bool
我现在想用 QuickCheck 测试这确实做了我想做的事情。但是,以下属性不起作用,因为 QuickCheck 只是放弃了:
subtypeSanity ∷ Tree (Type ()) → Gen Prop
subtypeSanity Node { rootLabel = t, subForest = f } =
let subtypes = concatMap flatten f
in (not $ null subtypes) ==> conjoin
(forAll (elements subtypes) (\x → x `subtypeOf` t):(map subtypeSanity f))
如果我省略对 的递归调用subtypeSanity
,即我传递给的列表的尾部,则conjoin
该属性运行良好,但只测试树的根节点!如何在 QuickCheck 不放弃生成新测试用例的情况下递归地进入我的数据结构?
如果需要,我可以提供构建 Schubert Hierarchy 的代码和 的Arbitrary
实例Tree (Type a)
,以提供完整的可运行示例,但这将是相当多的代码。我确信我只是没有“获得”QuickCheck,并且在这里以错误的方式使用它。
编辑:不幸的是,该sized
功能似乎并没有消除这里的问题。它最终得到相同的结果(请参阅对 J. Abrahamson 的回答的评论。)
编辑二:我最终通过避免递归步骤来“解决”我的问题,并避免conjoin
. 我们只需列出树中所有节点的列表,然后在这些节点上测试单节点属性(从一开始就很好)。
allNodes ∷ Tree a → [Tree a]
allNodes n@(Node { subForest = f }) = n:(concatMap allNodes f)
subtypeSanity ∷ Tree (Type ()) → Gen Prop
subtypeSanity tree = forAll (elements $ allNodes tree)
(\(Node { rootLabel = t, subForest = f }) →
let subtypes = concatMap flatten f
in (not $ null subtypes) ==> forAll (elements subtypes) (\x → x `subtypeOf` t))
调整树的Arbitrary
实例不起作用。这是我仍在使用的任意实例:
instance (Arbitrary a, Eq a) ⇒ Arbitrary (Tree (Type a)) where
arbitrary = liftM (constructH) $ sized arbTree
arbTree ∷ Arbitrary a ⇒ Int → Gen (Tree a)
arbTree n = do
m ← choose (0,n)
if m == 0
then Node <$> arbitrary <*> (return [])
else do part ← randomPartition n m
Node <$> arbitrary <*> mapM arbTree part
-- this is a crude way to find a sufficiently random x1,..,xm,
-- such that x1 + .. + xm = n, for any n, m, with 0 < m.
randomPartition ∷ Int → Int → Gen [Int]
randomPartition n m' = do
let m = m' - 1
seed ← liftM ((++[n]) . sort) $ replicateM m (choose (0,n))
return $ zipWith (-) seed (0:seed)
我认为这个问题“现在已经解决了”,但是如果有人可以向我解释为什么递归步骤和/或conjoin
让 QuickCheck 放弃(在通过“仅”0 个测试之后),我将不胜感激。