2015-04-11 1 views
6

Ich versuchte, die Introduction to Quickcheck zu folgen und wollte meine Funktion testen, die Zeichenfolgen mit Ziffern enthält. Dafür definierte ich eine Arbitrary Instanz für Char:Kann benutzerdefinierte `Arbitrary` Instanz für` Char` nicht definieren, da es bereits existiert

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

Aber ghc beklagt, dass:

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’ 

Wie kann ich ihm sagen, über die bereits definierten Instanz zu vergessen und meine eigene Instanz verwenden? Oder wird es überhaupt nicht funktionieren (was wäre seltsam, da das Tutorial diesen Ansatz verwendet)?

+7

Ich würde nur wickeln 'Char' in eine' newtype' wie 'newtype Digit = Digit Char' und diese – Carsten

+0

@ CarstenKönig in eine Instanz machen, sehr gute Problemumgehung. Verhindert auch, dass ich allgemeine Zeichen verwende, wo ich nur Ziffern verwenden soll. Weißt du, warum das nicht der Ansatz war, den das Tutorial genommen hat? – Turion

Antwort

11

Als @ carsten-könig beraten, wäre eine Lösung, eine newtype Wrapper für Char zu machen. Das ist keine Problemumgehung, aber eine richtige und wirklich nette Möglichkeit, eine ganze Klasse von Problemen zu umgehen, die verwaisten Instanzen (Instanzen für Typklassen, die in einem anderen Modul definiert sind) zugeordnet sind, lesen Sie mehr über solche Probleme here.

Darüber hinaus ist dieser Ansatz weit verbreitet, wenn es mehrere mögliche Instanzen mit unterschiedlichem Verhalten gibt.

Betrachten wir zum Beispiel die Monoid typeclass, die in Data.Monoid wie folgt definiert ist:

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

Wie Sie vielleicht schon wissen, Monoid eine Art von Werten, die aneinander gehängt werden kann (unter Verwendung von mappend) und für die Es existiert ein "Identitäts" -Wert mempty, der eine Regel mappend mempty a == a erfüllt (Anfügen einer Identität an Wert a ergibt a). Es ist offensichtlich, dass die Instanz von Monoid für Listen:

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

Es ist auch einfach Int s zu definieren. Tatsächlich bilden ganze Zahlen mit Additionsoperation ein korrektes Monoid.

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

Aber ist es das einzige mögliche Monoid für ganze Zahlen? Natürlich ist es nicht, Multiplikation mit ganzen Zahlen eine andere geeignete Monoid bilden würde:

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

Beide Instanzen korrekt sind, aber jetzt haben wir ein Problem: Wenn Sie 1 `mappend` 2 zu bewerten versuchen würde, auf keinen Fall ist es herauszufinden, welche Instanz muss verwendet werden.

Deshalb wickelt Data.Monoid die Instanzen für Zahlen in newtype Wrapper, nämlich Sum und Product.

Weiter zu gehen, Ihre Aussage

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

könnte sehr verwirrend sein. Es heißt "Ich bin ein beliebiger Charakter", produziert aber nur Ziffern. Meiner Meinung nach wäre das viel besser:

newtype DigitChar = DigitChar Char deriving (Eq, Show) 

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

Ein Stück Kuchen. Sie können weiter gehen und einen DigitChar Konstruktor verbergen, der digitChar 'intelligenten Konstruktor' zur Verfügung stellt, der nicht erlauben würde, eine DigitChar zu schaffen, die nicht wirklich eine Ziffer ist.

Als Ihre Frage "Weißt du, warum das ist nicht die Vorgehensweise, die das Tutorial dauerte?", Ich denke, der Grund ist einfach, das Tutorial scheint im Jahr 2006 geschrieben zu sein, und in those days quickcheck einfach nicht definiert Arbitrary Instanz für Char. So war der Code im Tutorial in den vergangenen Tagen perfekt.

+0

Das letzte Beispiel funktioniert nicht für mich. – orome

+0

@raxacoricofallapatorius Könnten Sie einige Details darüber geben, was nicht funktioniert? – zudov

+0

Ich bekomme: 'Konnte nicht erwarteten Typ 'Gen DigitChar' mit tatsächlichen Typ 'DigitChar' Im Ausdruck: DigitChar $ wählen ('0', '9')' – orome

0

Sie müssen keine neuen Arbitrary Instanzen für die Ad-hoc-Testeingabeerstellung erstellen. Sie können QuickCheck's forAll combinator verwenden explizit eine Gen a auf eine Funktion auszuwählen:

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

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