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
发生器?在后一种情况下,我实际上可能只是采用单元测试,因为这似乎太多了。
答案 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