2012-12-22 13 views
12

Załóżmy, że mam typ rekordu:Idiomatic sposób, aby zmniejszyć rekord w QuickCheck

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

Zgrabny sposób pisania arbitralne wystąpienie używa Control.Applicative tak:

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

lista kurczenie się Foo jest więc produktem kartezjańskim wszystkich kurczących się członków.

Ale jeśli jeden z tych kurczy się zwróci [], nie będzie żadnych kurczących się Foo jako całości. To nie działa.

mógłbym spróbować zapisać go w tym oryginalną wartość na liście skurczowej:

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

Ale teraz kurczyć (Foo 0 0 0) zwróci [Foo 0 0 0], co oznacza, że ​​kurczenie nigdy nie będzie zakończyć. To też nie działa.

Wygląda na to, że powinno być tutaj coś innego niż < *>, ale nie widzę co.

Odpowiedz

6

Nie wiem, co byłoby uznane za idiomatyczne, ale jeśli chcesz mieć pewność, że każdy kurczenie zmniejsza co najmniej jedno pole bez zwiększania innych,

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

zrobiłby tego. Instancja Applicative dla list jest taka, że ​​oryginalna wartość jest pierwszą na liście wyników, więc po prostu upuszczenie powoduje wyświetlenie listy wartości skurczonych, a więc maleje.

Jeśli chcesz, aby wszystkie pola były skurczone, jeśli to możliwe, a tylko niezmienne pola, które mają zostać zachowane, są nieco bardziej skomplikowane, musisz się komunikować, czy już udało Ci się uzyskać skurcz, czy nie, a jeśli nie masz nie dostałem żadnego na końcu, zwróć pustą listę. To, co spadło mi z głowy, to może ktoś wymyśli lepszy sposób na zrobienie tego.

+1

Myślę, że twoja pierwsza odpowiedź rozwiązuje natychmiastowy problem, dzięki. Poza tym coś podobnego do twojego drugiego może zrobić z dodaniem do QuickCheck –

8

Jeśli chcesz aplikacyjny funktor że skurczy się dokładnie w jednej pozycji, można cieszyć się ten jeden, który właśnie utworzony dokładnie porysować że grzybica:

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 

Używam go w kodzie, który wygląda tak , aby zmniejszyć albo w lewym elemencie krotki, lub w jednym z pól prawego elementu krotki:

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

działa świetnie do tej pory!

W rzeczywistości działa tak dobrze, że przesłałem go jako a hackage package.

Powiązane problemy