Search code examples
haskellgenericstestingquickcheck

How can I make my type an instance of Arbitrary?


I have the following data and function

data Foo = A | B deriving (Show)

foolist :: Maybe Foo -> [Foo]
foolist Nothing  = [A]
foolist (Just x) = [x]

prop_foolist x = (length (foolist x)) == 1

when running quickCheck prop_foolist, ghc tells me that Foo needs to be an instance of Arbitrary.

No instance for (Arbitrary Foo) arising from a use of  ‘quickCheck’
In the expression: quickCheck prop_foolist
In an equation for ‘it’: it = quickCheck prop_foolist

I tried data Foo = A | B deriving (Show, Arbitrary), but this results in

Can't make a derived instance of ‘Arbitrary Foo’:
  ‘Arbitrary’ is not a derivable class
  Try enabling DeriveAnyClass
In the data declaration for ‘Foo’

However, I can't figure out how to enble DeriveAnyClass. I just wanted to use quickcheck with my simple function! The possible values of x is Nothing, Just A and Just B. Surely this should be possible to test?


Solution

  • There are two reasonable approaches:

    Reuse an existing instance

    If there's another instance that looks similar, you can use it. The Gen type is an instance of Functor, Applicative, and even Monad, so you can easily build generators from other ones. This is probably the most important general technique for writing Arbitrary instances. Most complex instances will be built up from one or more simpler ones.

    boolToFoo :: Bool -> Foo
    boolToFoo False = A
    boolToFoo True = B
    
    instance Arbitrary Foo where
      arbitrary = boolToFoo <$> arbitrary
    

    In this case, Foo can't be "shrunk" to subparts in any meaningful way, so the default trivial implementation of shrink will work fine. If it were a more interesting type, you could have used some analogue of

      shrink = map boolToFoo . shrink . fooToBool
    

    Use the pieces available in Test.QuickCheck.Arbitrary and/or Test.QuickCheck.Gen

    In this case, it's pretty easy to just put together the pieces:

    import Test.QuickCheck.Arbitrary
    
    data Foo = A | B
      deriving (Show,Enum,Bounded)
    instance Arbitrary Foo where
      arbitrary = arbitraryBoundedEnum
    

    As mentioned, the default shrink implementation would be fine in this case. In the case of a recursive type, you'd likely want to add

    {-# LANGUAGE DeriveGeneric #-}
    import GHC.Generics (Generic)
    

    and then derive Generic for your type and use

    instance Arbitrary ... where
      ...
      shrink = genericShrink
    

    As the documentation warns, genericShrink does not respect any internal validity conditions you may wish to impose, so some care may be required in some cases.


    You asked about DeriveAnyClass. If you wanted that, you'd add

    {-# LANGUAGE DeriveAnyClass #-}
    

    to the top of your file. But you don't want that. You certainly don't want it here, anyway. It only works for classes that have a full complement of defaults based on Generics, typically using the DefaultSignatures extension. In this case, there is no default arbitrary :: Generic a => Gen a line in the Arbitrary class definition, and arbitrary is mandatory. So an instance of Arbitrary produced by DeriveAnyClass will produce a runtime error as soon as QuickCheck tries to call its arbitrary method.