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.

1 つのパラメーターのみ

引数が 1 つだけの関数に必要なものを提供する簡単なコードを次に示します。

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")

ご覧のとおり、アンカリー化すると、複数の引数を持つ関数で使用できます。それが十分でない場合は、少し難しくなりますが、型クラスの策略で可能になるはずです。

複数のパラメーター、戻り値の型が明示的にマークされている

これは、関数の戻り値を newtype でラップするために「のみ」を必要とするアプローチです。(これは、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 ()

したがって、「Testable」にある型を持つものを定義します。

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 に答える