如何为递归数据类型创建任意实例?

时间:2016-03-17 08:56:15

标签: haskell

我认为这会创建长度为3的任意列表,但是如何创建任意长度的列表?

import Test.QuickCheck

data List a =
  Nil
  | Cons a (List a)
  deriving (Eq, Show)

instance Arbitrary a  => Arbitrary (List a) where
  arbitrary = do
    a <- arbitrary
    a' <- arbitrary
    a'' <- arbitrary
    return $ (Cons a (Cons a' (Cons a'' (Nil))))

2 个答案:

答案 0 :(得分:7)

sized。它使您能够管理生成的arbitrary的大小,尽管语义取决于实例:

instance Arbitrary a => Arbitrary (List a)  where
  arbitrary = sized go
    where go 0 = pure Nil
          go n = do
            xs <- go (n - 1)
            x  <- arbitrary
            return (Cons x xs)

为了进行比较,这里是[]的{​​{3}}:

instance Arbitrary a => Arbitrary [a] where
  arbitrary = sized $ \n ->
    do k <- choose (0,n)
       sequence [ arbitrary | _ <- [1..k] ]

答案 1 :(得分:4)

您可以使用oneof选择空列表或递归生成更长的列表:

instance Arbitrary a  => Arbitrary (List a) where
  arbitrary = 
    oneof [nil, cons]
    where nil = return Nil
          cons = do
            h <- arbitrary
            tl <- arbitrary
            return $ Cons h tl 

这里有一些测试:

λ> generate (arbitrary :: Gen (List Int))
Nil
λ> generate (arbitrary :: Gen (List Int))
Cons 4 (Cons 26 Nil)
λ> generate (arbitrary :: Gen (List Int))
Nil
备注 zeta指出,这有一个明显的缺陷,你可能会产生非常短的列表:

  • p(Nil)= 0.5
  • p((_ Cons Nil)= 0.25
  • p((_ Cons _ Cons Nil)= 0.125
  • ...

因为它会以概率0.5

绘制Nil

Zetas解决方案没有这个问题!

如果您愿意,可以使用frequency代替oneof来调整这些概率:

frequency [(1,nil), (4,cons)]

在这里,您将拥有p(Nil) = 0.2p(Cons) = 0.8,但当然您可以根据自己的喜好进行调整。

另一种方法是认识到List a[a]是同构的,并将Arbitrary实例重用于列表:

instance Arbitrary a => Arbitrary (List a) where
    arbitrary = toList <$> arbitrary

谢谢Zeta