2012-12-22 4 views
12

nehme ich einen Plattentyp haben:Idiomatic Weg, um einen Datensatz in Quick schrumpfen

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

Eine saubere Art und Weise, eine beliebige Instanz des Schreibens verwendet Control.Applicative wie folgt aus:

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

Die Liste der Schrumpfung für ein Foo ist also das kartesische Produkt aller Schrumpfe seiner Mitglieder.

Aber wenn eine dieser Schrumpfungen zurückkehrt [], dann wird es keine Schrumpfungen für die Foo als Ganzes geben. Das funktioniert also nicht.

Ich versuche, es zu speichern konnte den ursprünglichen Wert in der Schrumpf Liste einschließlich:

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

Aber jetzt schrumpfen (Foo 0 0 0) zurückkehren [Foo 0 0 0], was bedeutet, dass Schrumpfen wird nie beenden. Das funktioniert auch nicht.

Es sieht so aus, als sollte etwas anderes als < *> hier verwendet werden, aber ich kann nicht sehen was.

Antwort

6

Ich weiß nicht, was würde idiomatische in Betracht gezogen werden, aber wenn Sie sicherstellen wollen, dass jedes Schrumpfen mindestens ein Feld reduziert, ohne die andere zu erhöhen,

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

würde das tun. Die Instanz für Listen ist so, dass der ursprüngliche Wert der erste in der Ergebnisliste ist. Wenn Sie also nur einen Wert löschen, erhalten Sie eine Liste mit wirklich geschrumpften Werten, wodurch die Schrumpfung beendet wird.

Wenn Sie möchten, dass alle Felder geschrumpft werden, wenn möglich, und nur nicht schrumpfbare Felder beibehalten werden, wie es ist, ist es ein wenig komplizierter, müssen Sie kommunizieren, ob Sie bereits einen erfolgreichen Schrumpfung oder nicht haben, und falls Sie haben hab am Ende nichts bekommen, gib eine leere Liste zurück. Was fiel von der Spitze meines Kopfes ist

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 

vielleicht kommt jemand mit einem schöneren Weg, das zu tun.

+1

Ich denke, Ihre erste Antwort löst das unmittelbare Problem, danke. Auch etwas wie Ihre zweite könnte mit QuickCheck hinzugefügt werden –

8

Wenn Sie eine applicative Funktors mögen, die in genau eine Position schrumpfen werden, können Sie diese eine genießen, die ich erstellt gerade genau, dass Juckreiz zu kratzen:

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 

ich es im Code verwenden, die wie folgt aussieht entweder im linken Elemente des Tupels, oder in eines der Felder des rechten Elements des Tupels zu schrumpfen,:

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

große Werke so weit!

Tatsächlich funktioniert es so gut, dass ich es als a hackage package hochgeladen habe.

Verwandte Themen