2012-12-22 29 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