読者です 読者をやめる 読者になる 読者になる

はわわーっ

はわわわわっ

QuickCheckのArbitraryのやつ

haskell

QuickCheckでテストに使われるランダムな値の設定とか。
QuickCheckを使うには Test.QuickCheck モジュールが必要。
参考:

ランダムに値を生成するのに arbitrary を使って
生成される値をみるには sample を使う。

>>> sample (arbitrary :: Gen Int)
-1
0
-1
0
13
-6
-71
105
535
-259
167
>>> sample (arbitrary :: Gen [Int])
[]
[2]
[4,4]
[-5,0]
[-17,-20,31,2]
[-3,48,-31,17,-28,-55,29]
[-123,-35,-37,-123,-104,-112,-74,117,38]
[-215,40]
[828,-416,309,-466,174,-184,-832]
[-495,-1223,-1162,-167,-280]
[-1138,-3239,988,-4023]
>>> sample (arbitrary :: Gen (Int, Int))
(0,-1)
(1,-1)
(-1,-1)
(3,3)
(7,-10)
(-37,-51)
(-9,91)
(63,-161)
(419,-284)
(-90,-1342)
(398,2179)

基本的な型はうまいことやってくれる。

自分で作った型にたいしては Arbitrary のインスタンスにしてから
arbitrary を定義する必要がある。

import Control.Monad
import Test.QuickCheck

newtype Foo a = Foo [(a, Int)] deriving Show
instance Arbitrary a => Arbitrary (Foo a) where
  arbitrary = liftM Foo arbitrary
>>> sample (arbitrary :: Gen (Foo Int))
Foo []
Foo []
Foo [(1,1),(-3,-2),(-3,-1),(1,2)]
Foo [(-2,-7),(0,-5),(8,-7),(3,7),(-8,0)]
Foo [(21,-27),(-10,24),(-30,30),(20,-6),(32,-9),(14,9),(18,25)]
Foo [(31,53),(-35,63)]
Foo [(-48,107),(2,107),(49,52),(-126,104),(-50,51),(122,22),
     (43,123),(66,95),(25,75),(-6,17)]
Foo [(168,-111),(-190,247),(208,89),(-135,-66),(224,149),
     (-255,109),(-190,-37),(157,-170),(-75,-70),(-168,206),
     (-209,-160),(-151,-62),(117,-199),(-178,-180)]
Foo [(-686,658),(290,207),(-373,-750),(-698,261),(-476,-280),
     (521,-621),(99,-470),(-223,-294)]
Foo [(1211,-1367),(1194,-1385),(1851,154),(-1907,1395),
     (709,-1570),(1228,685),(1696,-1531),(-895,-490),(-844,-263),
     (-87,-1009),(-782,-157),(-35,180),(2037,-142),(1264,-350),
     (-380,-1700),(-583,1761),(-630,-1731)]
Foo [(-1230,-2152),(2010,2306),(-2846,1768),(-3156,423),
     (-2203,-734),(422,-3753),(-1987,-2250),(487,-1455),(853,3130),
     (311,-1380),(-3843,-3857),(2656,1210),(2146,-2699),(-3824,2039),
     (-2624,3470),(3750,-813)]

これで自分で作った型もうまいことやってくれるんだけど、
場合によってはこれをちょっといじりたい時がある。
リストの中のタプルの2つ目の要素の和を100にしたいときは

import Control.Monad
import Test.QuickCheck

newtype Foo a = Foo [(a, Int)] deriving Show
instance Arbitrary a => Arbitrary (Foo a) where
  arbitrary = liftM Foo $ arbitrary' 100
    where
      arbitrary' 0 = return []
      arbitrary' n = do
        a <- arbitrary
        i <- choose (1, n)
        rest <- arbitrary' (n - i)
        return $ (a, i) : rest
>>> sample (arbitrary :: Gen (Foo Int))
Foo [(1,31),(-1,67),(0,2)]
Foo [(0,79),(1,1),(-2,9),(-1,3),(-2,5),(2,3)]
Foo [(0,96),(4,4)]
Foo [(-3,7),(1,42),(8,42),(4,6),(-2,2),(5,1)]
Foo [(-29,67),(24,21),(-9,8),(28,1),(14,3)]
Foo [(-50,25),(-4,5),(-42,18),(5,10),(-1,11),(59,7),(38,20),(-62,3),(-57,1)]
Foo [(-107,33),(25,40),(41,4),(48,21),(48,1),(-72,1)]
Foo [(117,28),(-235,39),(-91,30),(23,3)]
Foo [(-898,46),(-271,17),(130,26),(896,10),(-615,1)]
Foo [(1719,69),(-1911,12),(-2005,17),(807,2)]
Foo [(-690,93),(-3728,2),(444,4),(-430,1)]

こんな感じにやるとできた。

せっかくなので本当に和が100になってるかQuuckCheckでテストしてみる

sumCheck :: Foo Int -> Bool
sumCheck (Foo xs) = sum (map snd xs) == 100
>>> quickCheck sumCheck
+++ OK, passed 100 tests.

ちゃんとできてるみたい。