Search code examples
haskellpolymorphismtypeclassparametric-polymorphism

Non type-variable argument in the constraint for Arbitrary typeclass


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?


Solution

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