2012-12-22 30 views
12

假設我有一個記錄類型:慣用的方式來收縮紀錄快速檢查

data Foo = Foo {x, y, z :: Integer} 

寫一個任意實例的一個巧妙的辦法使用Control.Applicative這樣的:

instance Arbitrary Foo where 
    arbitrary = Foo <$> arbitrary <*> arbitrary <*> arbitrary 
    shrink f = Foo <$> shrink (x f) <*> shrink (y f) <*> shrink (z f) 

名單Foo的收縮因此是其成員所有縮小的笛卡爾積。

但是,如果其中一個收縮返回[],那麼整個Foo將不會收縮。所以這是行不通的。

我可以嘗試通過在收縮列表中的原始值將其保存:

shrink f = Foo <$> ((x f) : shrink (x f)) <*> ... {and so on}. 

但現在萎縮(美孚0 0 0)將返回[美孚0 0 0],這意味着收縮永遠終止。所以這也行不通。

它看起來應該有其他的東西< *>在這裏使用,但我看不到什麼。

回答

6

我不知道怎樣纔算地道,但如果你想確保每一個收縮減少了至少一個字段不增加其他人,

shrink f = tail $ Foo <$> shrink' (x f) <*> shrink' (y f) <*> shrink' (z f) 
    where 
    shrink' a = a : shrink a 

會做到這一點。用於列表的Applicative實例是這樣的,即原始值是結果列表中的第一個值,因此只要刪除就可以得到真正縮小的值列表,因此縮小會終止。

如果您希望所有字段在可能的情況下收縮,並且只保留不可縮放的字段,則會稍微複雜一些,您需要告知您是否已成功收縮或不收縮,以及如果您避難最後得到的,返回一個空的列表。什麼掉在我的頭頂是

data Fallback a 
    = Fallback a 
    | Many [a] 

unFall :: Fallback a -> [a] 
unFall (Fallback _) = [] 
unFall (Many xs) = xs 

fall :: a -> [a] -> Fallback a 
fall u [] = Fallback u 
fall _ xs = Many xs 

instance Functor Fallback where 
    fmap f (Fallback u) = Fallback (f u) 
    fmap f (Many xs) = Many (map f xs) 

instance Applicative Fallback where 
    pure u = Many [u] 
    (Fallback f) <*> (Fallback u) = Fallback (f u) 
    (Fallback f) <*> (Many xs) = Many (map f xs) 
    (Many fs) <*> (Fallback u) = Many (map ($ u) fs) 
    (Many fs) <*> (Many xs) = Many (fs <*> xs) 

instance Arbitrary Foo where 
    arbitrary = Foo <$> arbitrary <*> arbitrary <*> arbitrary 
    shrink f = unFall $ Foo <$> shrink' (x f) <*> shrink' (y f) <*> shrink' (z f) 
     where 
     shrink' a = fall a $ shrink a 

也許有人想出了一個更好的方式來做到這一點。

+1

我認爲你的第一個答案解決了眼前的問題,謝謝。此外,像你的第二個可以做的就是添加到QuickCheck –

8

如果你想要一個適用函子將在恰好一個位置縮水,你可能會喜歡這一個,我剛創建精確劃傷癢:

data ShrinkOne a = ShrinkOne a [a] 

instance Functor ShrinkOne where 
    fmap f (ShrinkOne o s) = ShrinkOne (f o) (map f s) 

instance Applicative ShrinkOne where 
    pure x = ShrinkOne x [] 
    ShrinkOne f fs <*> ShrinkOne x xs = ShrinkOne (f x) (map ($x) fs ++ map f xs) 

shrinkOne :: Arbitrary a => a -> ShrinkOne a 
shrinkOne x = ShrinkOne x (shrink x) 

unShrinkOne :: ShrinkOne t -> [t] 
unShrinkOne (ShrinkOne _ xs) = xs 

我使用它的代碼看起來像這樣,縮小元組的左側元素或元組右側元素的一個字段中的縮小:

shrink (tss,m) = unShrinkOne $ 
    ((,) <$> shrinkOne tss <*> traverse shrinkOne m) 

迄今爲止效果很好!

事實上,它工作得很好,我上傳它爲a hackage package