2

我正在尝试使用 quickcheck 来生成给定函数的随机参数(假设它的所有类型都有 Arbitrary 实例和 Show 实例)以及在这些参数处对函数的评估。我只需要打印参数的值并在之后评估答案。所以我期望一个具有以下类型的函数

randomEvaluate :: Testable a => a -> IO ( [String] -- arguments
                                        , String ) -- Answer after evaluating
                                                   -- IO is just needed to get a new random number generator. If I pass a generator then I think probably I will not need IO here. 

我仍然不确定这里的类型,但我认为Testable a可以。我仍然无法真正得到我需要的东西。Rose我对快速检查数据类型等的混乱感到困惑Result

更新

假设我有一个功能

add :: Int -> Int -> Int
add a b = a+b

然后我假设一个行为像

> randomEvaluate add
(["1","3"],"4")

其中 1 和 3 是为 生成的随机值,Int而 4 是f 1 3

4

2 回答 2

9

我认为除了模块Test.QuickCheck.ArbitraryTest.QuickCheck.Gen.

只有一个参数

下面是一些简单的代码,它只提供了一个参数的函数所需的内容:

import Test.QuickCheck.Arbitrary
import Test.QuickCheck.Gen
import System.Random

randomEvaluate :: (Arbitrary a, Show a, Show b) => (a -> b) -> IO (String, String)
randomEvaluate f = do
    stdGen <- newStdGen
    let x = unGen arbitrary stdGen 1000
    let y = f x
    return (show x, show y)

在这里你可以看到它的实际效果:

*Main> randomEvaluate (\(a,b) -> a + b)
("(-292,-655)","-947")
*Main> randomEvaluate (\(a,b) -> a + b)
("(586,-905)","-319")
*Main> randomEvaluate (\(a,b) -> a + b)
("(547,-72)","475")

如您所见,如果您对它进行 uncurry,则可以将它与具有多个参数的函数一起使用。如果这还不够的话,事情会变得有点困难,但应该可以通过一些类型类技巧来实现。

多个参数,显式标记的返回类型

这是一种需要“仅”将函数的返回值包装在新类型中的方法。(使用非 Haskell98 功能可能可以避免这种情况):

class RandEval a where
    randomEvaluate :: StdGen -> a -> ([String], String)

newtype Ret a = Ret a

instance Show a => RandEval (Ret a)  where
    randomEvaluate _ (Ret x) = ([], show x)

instance (Show a, Arbitrary a, RandEval b) => RandEval (a -> b) where
    randomEvaluate stdGen f = (show x : args, ret)
        where (stdGen1, stdGen2) = split stdGen
              x = unGen arbitrary stdGen1 1000
              (args, ret) = randomEvaluate stdGen2 (f x) 

doRandomEvaluate :: RandEval a => a -> IO ([String], String)
doRandomEvaluate f = do
    stdGen <- newStdGen
    return $ randomEvaluate stdGen f

在这里查看它的实际效果:

*Main> doRandomEvaluate (\a b -> Ret (a && b))
(["False","True"],"False")
*Main> doRandomEvaluate (\a b -> Ret (a + b))
(["944","758"],"1702")
*Main> doRandomEvaluate (\a b c -> Ret (a + b + c))
(["-274","413","865"],"1004")
*Main> doRandomEvaluate (\a b c d -> Ret (a + b + c + d))
(["-61","-503","-704","-877"],"-2145")

带有语言扩展的多个参数

如果还不需要显式标记返回值,这可行,但使用语言扩展:

{-# LANGUAGE FlexibleInstances, UndecidableInstances, OverlappingInstances #-}

import Test.QuickCheck.Arbitrary
import Test.QuickCheck.Gen
import System.Random
import Control.Arrow

class RandEval a where
    randomEvaluate :: StdGen -> a -> ([String], String)

instance (Show a, Arbitrary a, RandEval b) => RandEval (a -> b) where
    randomEvaluate stdGen f = first (show x:) $ randomEvaluate stdGen2 (f x) 
        where (stdGen1, stdGen2) = split stdGen
              x = unGen arbitrary stdGen1 1000

instance Show a => RandEval a where
    randomEvaluate _ x = ([], show x)

doRandomEvaluate :: RandEval a => a -> IO ([String], String)
doRandomEvaluate f = do
    stdGen <- newStdGen
    return $ randomEvaluate stdGen f

这是帖子中的原始用例:

*Main> doRandomEvaluate ( (+) :: Int -> Int -> Int )
(["-5998437593420471249","339001240294599646"],"-5659436353125871603")

但现在您对 GHC 如何解决重叠实例一时心血来潮。例如,即使使用这个不错的(但也不是 Haskell98)实例来显示布尔函数:

type BoolFun a = Bool -> a

instance Show a => Show (BoolFun a) where
    show f = "True -> " ++ show (f True) ++ ", False -> " ++ show (f False)

aBoolFun :: Bool -> BoolFun Bool
aBoolFun x y = x && y

您看不到此实例在使用中doRandomEvaluate

*Main> doRandomEvaluate aBoolFun 
    (["False","False"],"False")

使用原始解决方案,您可以:

*Main> doRandomEvaluate (Ret . aBoolFun)
(["False"],"True -> False, False -> False")
*Main> doRandomEvaluate (Ret . aBoolFun)
(["True"],"True -> True, False -> False")

一个警告

但请注意,这是一个滑坡。对上面的代码做一个小改动,它在 GHC 7.6.1 中停止工作(但在 GHC 7.4.1 中仍然有效):

instance (Show a, Arbitrary a, RandEval b) => RandEval (a -> b) where
    randomEvaluate stdGen f = (show x:args, ret)
        where (stdGen1, stdGen2) = split stdGen
              x = unGen arbitrary stdGen1 1000
              (args, ret) = randomEvaluate stdGen2 (f x) 

SPJ 解释了为什么这不是一个真正的错误——对我来说这是一个明显的迹象,表明这种方法将类型类黑客技术推得太远了。

于 2013-01-12T16:10:09.593 回答
3

QuickCheck 非常简单:

Prelude> import Test.QuickCheck

提供了一个简单的驱动函数:

Prelude Test.QuickCheck> :t quickCheck
quickCheck :: Testable prop => prop -> IO ()

所以定义一些在“可测试”中找到的类型:

Prelude Test.QuickCheck> let prop_commut a b = a + b == b + a
Prelude Test.QuickCheck> :t prop_commut
prop_commut :: (Eq a, Num a) => a -> a -> Bool

并运行它:

Prelude Test.QuickCheck> quickCheck prop_commut 
+++ OK, passed 100 tests.

如需更全面的治疗,请参阅 RWH

于 2013-01-12T15:36:12.957 回答