2015-04-11 16 views
6

Ho provato a seguire lo Introduction to Quickcheck e ho voluto testare la mia funzione che utilizza stringhe contenenti cifre. Per questo, ho definito un'istanza Arbitrary per Char:Impossibile definire l'istanza "Arbitrary" personalizzata per "Char" poiché esiste già

instance Arbitrary Char where 
    arbitrary = choose ('0', '9') 

Ma ghc lamenta che:

A.hs:16:10: 
Duplicate instance declarations: 
    instance Arbitrary Char -- Defined at A.hs:16:10 
    instance [overlap ok] [safe] Arbitrary Char 
    -- Defined in ‘Test.QuickCheck.Arbitrary’ 

Come faccio a dirgli di dimenticare l'istanza già definite e usare il mio esempio? O non funzionerà affatto in questo modo (il che sarebbe strano, dato che il tutorial prende questo approccio)?

+7

vorrei solo avvolgere 'char' in un' newtype' come 'Newtype cifra = Cifra char' e fare questo in un caso – Carsten

+0

@ CarstenKönig, molto buona soluzione. Inoltre mi impedisce di usare caratteri generali in cui dovrei usare solo cifre. Sai perché non è questo l'approccio che ha preso il tutorial? – Turion

risposta

11

Come consigliato da @ carsten-könig, una soluzione sarebbe quella di creare un wrapper newtype per Char. Questa non è una soluzione alternativa, ma un modo corretto e davvero efficace per sfuggire a un'intera classe di problemi correlati alle istanze orfane (istanze per le classi di caratteri definite in un altro modulo), maggiori informazioni su tali problemi here.

Inoltre, questo approccio è ampiamente utilizzato quando ci sono diverse istanze con comportamenti diversi.

Ad esempio, si consideri il Monoid typeclass che è definito in Data.Monoid come:

class Monoid a where 
    mempty :: a   --^Identity of 'mappend' 
    mappend :: a -> a -> a --^An associative operation 

Come si può già sapere Monoide è un tipo di valori che possono essere aggiunto tra loro (mediante mappend) e per i quali esiste un valore di "identità" mempty che soddisfa una regola mappend mempty a == a (aggiunta di un'identità al valore a in a). C'è un caso evidente di Monoide per gli elenchi:

class Monoid [a] where 
    mempty = [] 
    mappend = (++) 

E 'anche facile da definire Int s. Infatti gli interi con l'operazione di addizione formano un monoide corretto.

class Monoid Int where 
    mempty = 0 
    mappend = (+) 

Ma è l'unico monoid possibile per numeri interi? Naturalmente non è, la moltiplicazione su interi sarebbe formare un altro adeguato monoide:

class Monoid Int where 
    mempty = 1 
    mappend = (*) 

Entrambe le istanze sono corrette, ma ora abbiamo un problema: se si desidera cercare di valutare 1 `mappend` 2, non c'è modo di capire quale istanza deve essere utilizzata.

Ecco perché Data.Monoid avvolge le istanze per i numeri in wrapper newtype, ovvero Sum e Product.

Andando oltre, la sua dichiarazione

instance Arbitrary Char where 
    arbitrary = choose ('0', '9') 

potrebbe essere molto confusa. Dice "Sono un personaggio arbitrario" ma produce solo caratteri numerici. A mio parere, sarebbe molto meglio:

newtype DigitChar = DigitChar Char deriving (Eq, Show) 

instance Arbitrary DigitChar where 
    arbitrary = fmap DigitChar (choose ('0', '9')) 

Un pezzo di torta. Puoi andare oltre e nascondere un costruttore DigitChar, fornendo "costruttore intelligente" digitChar, che non consentirebbe di creare un DigitChar che in realtà non è una cifra.

quanto della tua domanda "Sai perché non è questo l'approccio del tutorial ha preso?", Penso che il motivo è semplice, il tutorial sembra essere scritto nel 2006, e in those days quickcheck semplicemente non ha definito un Arbitrary istanza per Char. Quindi il codice nel tutorial era perfettamente valido nel passato.

+0

Quest'ultimo esempio non funziona per me. – orome

+0

@raxacoricofallapatorius Potresti fornire alcuni dettagli su cosa non funziona? – zudov

+0

Ottengo: 'Impossibile non corrispondere al tipo previsto 'Gen DigitChar' con il tipo effettivo 'DigitChar' Nell'espressione: DigitChar $ scegliere ('0', '9')' – orome

0

Non è necessario creare nuove istanze Arbitrary per la generazione di input di test ad hoc. È possibile utilizzare QuickCheck's forAll combinator per scegliere in modo esplicito un Gen a a una funzione:

digit :: Gen Char 
digit = choose ('0', '9) 

prop_myFun = forAll digit $ \x -> isAwesome (myFun x)