在范围内生成任意的最小限制

时间:2015-03-23 13:29:30

标签: haskell testing quickcheck

我想为有序的树状结构生成任意值,其类型为

data Tree a = Leaf | Node (Tree a) a (Tree a)

在保持命令的同时将值插入此树的函数将要求该值应为Ord。但是为了为任意实例生成有序树,我需要在范围“[low,hi]”中生成值,而Ord不足以实现。因此,我还要求该值为Enum,因为Enum允许从给定边界获取值。下面的choose也需要System.Random.Random:

import Test.QuickCheck.Arbitrary
import System.Random

generateTree :: (Arbitrary a, Ord a, Enum a, Random a) => a -> a -> Gen (Tree a)
generateTree l u = do
  genLeaf <- arbitrary
  if genLeaf
    then return Leaf
    else do
      x <- choose (l, u)
      left <- generateTree l (pred x)
      right <- generateTree (succ x) u
      return $ Node left x right

所以要将它用于arbitrary实现,我应该在Arbitrary类中需要相同的类:

instance (Arbitrary a, Ord a, Enum a, Rand.Random a) => Arbitrary (Tree a) where
  arbitrary = do
    l <- arbitrary
    u <- arbitrary
    generateTree l u

虽然需求Ord a => Tree a对于数据结构本身来说已经足够了,但我需要(Arbitrary a, Ord a, Enum a, Rand.Random a) => Tree a来生成它。它似乎将实现细节泄露到声明中 - 可能我看错了这个方法,我正在学习Haskell。但仍有问题:

  1. 有没有办法声明没有那么多要求的更通用的生成器?
  2. 有没有办法为Arbitrary (Tree a)声明其他实例以及一个具有不同要求集的实例,例如仅用于Arbitrary (Tree Int)的{​​{1}}?< / LI>

1 个答案:

答案 0 :(得分:4)

由于您需要任意有序树,因此无法摆脱Ord a实例中的Arbitrary (Tree a)约束。但是,您不需要EnumRandom(回想Arbitrary正在进行随机值生成 - 所以您自然也不需要随机数)。

以下是我将如何实现Arbitrary实例。

import Test.QuickCheck.Gen
import Test.QuickCheck

arbitraryTree :: (Ord a, Arbitrary a) => Gen (Tree a)
arbitraryTree = do 
  x <- arbitrary 
  y <- suchThat arbitrary (>= x) 
  go x y 
   where 
    go mn mx = frequency [(1, return Leaf), (1, arbNode)] where

      arbNode = do 
        a <- suchThat arbitrary (\x -> x >= mn && x <= mx)
        l <- go mn a
        r <- go a mx 
        return (Node l a r) 

instance (Ord a, Arbitrary a) => Arbitrary (Tree a) where 
  arbitrary = arbitraryTree 

请注意,如果在一定次数的尝试中找不到合适的值,则可以使用suchThatMaybe并提供默认值,从而使其更可靠。

关于你的第二个问题,你确实可以拥有以下实例:

{-# LANGUAGE OverlappingInstances, FlexibleInstances #-}

instance Arbitrary (Tree a)
instance Arbitrary (Tree Int)

并且仅当Tree a不是a时,编译器才会选择Int实例。但是,我建议不要这样做 - 如果您主要对测试Tree Int或甚至一小组Tree类型感兴趣,那么只需拥有这些类型的实例 - Tree a没有通用实例。


顺便说一下,我上面给出的生成器不是很好 - 它只产生Leaf一半的时间。原则上,我希望像这样的类型的生成器始终生成“中等”大小的树。实现这一点的一种简单方法是最初生成Node的概率很高,并随着树的增长而稳定地降低概率:

arbitraryTree :: (Ord a, Arbitrary a) => Float -> Float -> Gen (Tree a)
arbitraryTree c' f = do 
  x <- arbitrary 
  y <- suchThat arbitrary (>= x) 
  go x y c' 
   where 
    go mn mx c = frequency [(1000-q, return Leaf), (q, arbNode)] where
      q = round (1000 * c)

      arbNode = do 
        a <- suchThat arbitrary (\x -> x >= mn && x <= mx)
        l <- go mn a (c * f) 
        r <- go a mx (c * f) 
        return (Node l a r) 

instance (Ord a, Arbitrary a) => Arbitrary (Tree a) where 
  arbitrary = arbitraryTree 0.9 0.9 

您可以使用arbitraryTree的参数来获取您喜欢的树。