For an exercise in Chapter 15 of Haskell Programming From First Principles, I'm trying to write an Arbitrary
instance based on another Arbitrary
instance:
module AccumulateRight where
import Data.Semigroup
import Test.QuickCheck
data Validation a b = Fail a | Pass b deriving (Eq, Show)
newtype AccumulateRight a b =
AccumulateRight (Validation a b) deriving (Eq, Show)
type TestType = AccumulateRight String [Int]
instance Semigroup b => Semigroup (AccumulateRight a b) where
_ <> (AccumulateRight (Fail x)) = Fail x
(AccumulateRight (Fail x)) <> _ = Fail x
(AccumulateRight (Success a)) <> (AccumulateRight (Success b)) =
AccumulateRight . Success $ a <> b
instance (Arbitrary a, Arbitrary b) => Arbitrary (Validation a b) where
arbitrary = oneof [Fail <$> arbitrary, Pass <$> arbitrary]
instance Arbitrary (Validation a b) => Arbitrary (AccumulateRight a b) where
arbitrary = AccumulateRight <$> arbitrary
semigroupAssoc :: (Eq m, Semigroup m) => m -> m -> m -> Bool
semigroupAssoc a b c = (a <> (b <> c)) == ((a <> b) <> c)
type Assoc = TestType -> TestType -> TestType -> Bool
main :: IO ()
main = quickCheck (semigroupAssoc :: Assoc)
but the following error occurs:
• Non type-variable argument
in the constraint: Arbitrary (Validation a b)
(Use FlexibleContexts to permit this)
• In the context: Arbitrary (Validation a b)
While checking an instance declaration
In the instance declaration for ‘Arbitrary (AccumulateRight a b)’
|
22 | instance Arbitrary (Validation a b) => Arbitrary (AccumulateRight a b) where
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Failed, no modules loaded.
So am I doing anything wrong here? Why can't I use the typeclass of an existing data as the constraint here?
It's a silly restriction that was put in place before it was understood how difficult typeclasses would be to implement. Turns out it's easy enough to support, so there's a language extension -- mentioned in the error -- that lets you say that. You can turn it on by adding
{-# LANGUAGE FlexibleContexts #-}
to the top of your file, and as extensions go this one is considered completely benign. However, in this case, you should not turn it on, and instead should just write
instance (Arbitrary a, Arbitrary b) => Arbitrary (AccumulateRight a b)
-- after all, (Arbitrary a, Arbitrary b)
are exactly the conditions under which Arbitrary (Validation a b)
holds.