Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can't define custom `Arbitrary` instance for `Char` since it already exists

I tried following the Introduction to Quickcheck and wanted to test my function which takes strings containing of digits. For that, I defined an Arbitrary instance for Char:

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

But ghc complains about that:

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’

How can I tell it to forget about the already defined instance and use my own instance? Or will it not work that way at all (which would be strange, since the tutorial takes that approach)?

like image 452
Turion Avatar asked Apr 11 '15 12:04

Turion


1 Answers

As @carsten-könig adviced, a solution would be to make a newtype wrapper for Char. This is not a workaround, but a proper and really nice way to escape a whole class of problems that are related to orphan instances (instances for typeclasses which are defined in another module), read more about such problems here.

Moreover, this approach is widely used when there are several possible instances with different behaviour.

For example, consider the Monoid typeclass which is defined in Data.Monoid as:

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

As you may already know Monoid is a type of values which can be appended to each other (using mappend) and for which there exists an 'identity' value mempty which satisfies a rule mappend mempty a == a (appending an identity to value a results in a). There is an obvious instance of Monoid for Lists:

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

It's also easy to define Ints. Indeed integers with addition operation form a correct monoid.

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

But is it the only possible monoid for integers? Of course it isn't, multiplication on integers would form another proper monoid:

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

Both instances are correct, but now we have a problem: if you would try to evaluate 1 `mappend` 2, there is no way to figure out which instance must be used.

That's why Data.Monoid wraps the instances for numbers into newtype wrappers, namely Sum and Product.

Going further, your statement

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

might be very confusing. It says "I am an arbitrary character" but produces only digit characters. In my opinion this would be much better:

newtype DigitChar = DigitChar Char deriving (Eq, Show)

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

A piece of cake. You can go further and hide a DigitChar constructor, providing digitChar 'smart constructor', which wouldn't allow to create a DigitChar which is not actually a digit.

As of your question "Do you know why that's not the approach the tutorial took?", I think the reason is simple, the tutorial seems to be written in 2006, and in those days quickcheck simply didn't define an Arbitrary instance for Char. So the code in tutorial was perfectly valid back in the days.

like image 120
zudov Avatar answered Sep 28 '22 11:09

zudov