I have the type and it is an instance of SemiGroup. I want to write a quickCheck method to ensure that it is correct. How do I create an Arbitrary instance of this type?
newtype Combine a b =
Combine { unCombine :: a -> b }
instance Semigroup b => Semigroup (Combine a b) where
x <> y = Combine $ \n -> unCombine x n <> unCombine y n
instance (Q.Arbitrary a, Num a) => Q.Arbitrary (Combine a b) where
arbitrary = do
a <- Q.arbitrary
return $ Combine (\n -> Sum(n+1)) a
The minimal fix is to turn on FlexibleInstances
and write:
instance (Q.Arbitrary a, Num a) => Q.Arbitrary (Combine a (Sum a)) where
arbitrary = do
a <- Q.arbitrary :: Q.Gen ()
return $ Combine (\n -> Sum(n+1))
However, this is a bit unsatisfactory: I observe that a
is completely unused (hence has to be manually given a type signature!), and the distribution over functions is kind of boring, since it returns the function (+1)
(up to newtype wrapping) with probability 1. (Perhaps you wanted return $ Combine (\n -> Sum (n+a))
? But even then the class of functions you can produce this way is a little bit boring. I'm not sure what you really meant, and I won't spend long speculating.)
If you want to do it right, you should produce a random output for each input. This is pretty convenient with the universe
package:
import Data.Universe
import Data.Universe.Instances.Reverse
instance (Ord a, Finite a, Q.Arbitrary b) => Q.Arbitrary (Combine a b) where
arbitrary = Combine <$> sequenceA (const Q.arbitrary)
As a side benefit, this does not require any extensions. However, you should be careful to choose input types with small domains -- Combine Bool (Sum Int)
should be fine, or even Combine Word8 (Sum Word8)
, but if you try to test a property on a type like Combine Int (Sum Int)
you will be sorry!