Search code examples
haskellfunctorquickcheck

QuickChecking simple Functors: Is defining an Arbitrary instance necessary ? Why ? How?


I'm doing exercise with Functors and QuickCheck.

I have a super simple Functor, whose composition law I wish to quickCheck. The Functor is simply an Identity a. This is the code I have so far:

import Data.Functor
import Test.QuickCheck

newtype Identity a = Identity a

instance (Eq a) => Eq (Identity a) where
    (==) (Identity x) (Identity y) = x == y
    (/=) (Identity x) (Identity y) = x /= y

instance Functor Identity where
    fmap f (Identity x) = Identity (f x)

propertyFunctorCompose ::(Eq (f c), Functor f) => (a -> b) -> (b -> c) -> f a -> Bool
propertyFunctorCompose f g fr = (fmap (g . f) fr) == (fmap g . fmap f) fr

main = do
    quickCheck $ \x -> propertyFunctorCompose (+1) (*2) (x :: Identity Int) 

Unfortunately this code does not compile, and ghc complains with this compilation error:

functor_exercises.hs:43:5:
    No instance for (Arbitrary (Identity Int))
      arising from a use of `quickCheck'
    Possible fix:
      add an instance declaration for (Arbitrary (Identity Int))
    In the expression: quickCheck
    In a stmt of a 'do' block:
      quickCheck $ \ x -> propertyFunctorId (x :: Identity Int)
    In the expression:
      do { quickCheck $ \ x -> propertyFunctorId (x :: [Int]);
           quickCheck
           $ \ x -> propertyFunctorCompose (+ 1) (* 2) (x :: [Int]);
           quickCheck (propertyFunctorCompose' :: IntFC);
           quickCheck $ \ x -> propertyFunctorId (x :: Identity Int);
           .... }

So I have started to look at the QuickCheck Arbitrary typeclass, and it needs to define arbitrary :: Gen a and shrink :: a -> [a].

I have the (maybe false) feeling, that I should not need to define the Arbitrary instance for such a simple functor.

And if I really need to define the instance Arbitrary for Identity, then I have no idea what arbitrary and shrink should look like and how they should behave.

Could you guide me on this ?


Solution

  • You sure need the instance in order to work with quickcheck.

    But, because this functor is so simple, that's pretty trivial: Identity A is isomorphic to A itself, so it also permits the exact same Arbitrary instance. It's basically the same deal as with your Eq instance.

    instance (Arbitrary a) => Arbitrary (Identity a) where
        arbitrary = Identity <$> arbitrary
        shrink (Identity v) = Identity <$> shrink v