QuickCheck:生成平衡样本的嵌套数据结构的任意实例

时间:2013-04-11 21:40:16

标签: testing haskell data-structures quickcheck

tl; dr:如果您的数据类型允许过多嵌套,您如何编写不会爆炸的Arbitrary实例?您如何保证这些实例能够生成真正随机的数据结构样本?

我想生成随机树结构,然后在用我的库代码修改它们之后测试这些结构的某些属性。 (注意:我正在编写子类型算法的实现,即给定类型的层次结构,类型A是类型B的子类型。通过在层次结构中包含多继承和后初始化更新,可以使其任意复杂化不支持这些的经典方法是舒伯特编号,我所知道的最新结果是Alavi et al.2008。)

让我们以Data.Tree之后的玫瑰树为例:

data Tree a = Node a (Forest a)
type Forest a = [Tree a]

Arbitray的一个非常简单的(并且不在此尝试)实例将是:

instance (Arbitrary a) => Arbitrary (Tree a) where
    arbitrary = Node <$> arbitrary <$> arbitrary

由于a已根据类型约束已有Arbitrary个实例,而Forest将有一个,因为[]也是一个实例,这似乎是直的-向前。它不会(通常)以非常明显的原因终止:因为它生成的列表是任意长的,结构变得太大,并且它们很可能不适合内存。即使是更保守的方法:

arbitrary = Node <$> arbitrary <*> oneof [arbitrary,return []]
由于同样的原因,

再次无效。人们可以调整大小参数,以保持列表的长度,但即使这样也不能保证终止,因为它仍然是多个连续的骰子滚动,并且它可能会非常糟糕(和我想要有100个孩子的奇数节点。)

这意味着我需要限制整个树的大小。那不是那么直截了当。 unordered-containers让事情变得简单:只需使用fromList即可。这在这里不是那么容易:你如何随机地将一个列表变成一棵树,而不会产生某种方式的偏见(即不利于左分支,或者左倾的树木。)

来自列表的某种广度优先构造(由Data.Tree提供的功能都是预订的)会很棒,而且我想我可以写一个,但它会变得非常重要。由于我现在正在使用树,但是后来会使用更复杂的东西,我想我可能会尝试找到更通用,更简单的解决方案。有没有,还是我不得不求助于编写我自己的非平凡Arbitrary发生器?在后一种情况下,我实际上可能只是采用单元测试,因为这似乎太多了。

3 个答案:

答案 0 :(得分:20)

使用sized

instance Arbitrary a => Arbitrary (Tree a) where
arbitrary = sized arbTree

arbTree :: Arbitrary a => Int -> Gen (Tree a)
arbTree 0 = do
    a <- arbitrary
    return $ Node a []
arbTree n = do
    (Positive m) <- arbitrary
    let n' = n `div` (m + 1)
    f <- replicateM m (arbTree n')
    a <- arbitrary
    return $ Node a f

(改编自the QuickCheck presentation)。

P.S。也许这会产生过度平衡的树......

答案 1 :(得分:7)

您可能希望使用Haskell Symposium 2012中的“特性:代数类型的功能枚举”一文中提供的库。它在Hackage上作为测试专长,并且可以在此处获得介绍它的视频: http://www.youtube.com/watch?v=HbX7pxYXsHg

答案 2 :(得分:2)

正如Janis所提到的,您可以使用包testing-feat,它创建任意代数数据类型的枚举。这是创建无偏均匀分布式发电机的最简单方法 对于所有特定大小的树木。

以下是将它用于玫瑰树的方法:

import Test.Feat (Enumerable(..), uniform, consts, funcurry)
import Test.Feat.Class (Constructor)
import Data.Tree (Tree(..))
import qualified Test.QuickCheck as QC

-- We make an enumerable instance by listing all constructors
-- for the type. In this case, we have one binary constructor:
-- Node :: a -> [Tree a] -> Tree a
instance Enumerable a => Enumerable (Tree a) where
    enumerate = consts [binary Node]
      where
        binary :: (a -> b -> c) -> Constructor c
        binary = unary . funcurry

-- Now we use the Enumerable instance to create an Arbitrary
-- instance with the help of the function:
-- uniform :: Enumerable a => Int -> QC.Gen a
instance Enumerable a => QC.Arbitrary (Tree a) where
    QC.arbitrary = QC.sized uniform
    -- QC.shrink = <some implementation>

也可以使用TemplateHaskell自动生成Enumerable实例:

deriveEnumerable ''Tree