Search code examples
haskellquickcheck

Haskell Arbitrary Instance of Higher Order type


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

Solution

  • 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!