haskellsemigroup

How to write `Semigroup` instance and their `quickCheck`s on parameterized types?


In the exercises of Haskell Programming from First Principle book on Semigroup, I am asked to write quickCheck for user defined typeclasses. There are many typeclasses, but I do not understand how to write even the basic ones:

Problems:

The first is for Trivial:

module Exercise where

import Test.QuickCheck

data Trivial =
  Trivial
  deriving (Eq, Show)

instance Semigroup Trivial where
  _ <> _ = undefined

instance Arbitrary Trivial where
  arbitrary = return Trivial

semigroupAssoc :: (Eq m, Semigroup m) => m -> m -> m -> Bool
semigroupAssoc a b c = (a <> (b <> c)) == ((a <> b) <> c)

type TrivialAssoc = Trivial -> Trivial -> Trivial -> Bool

The second is for

newtype Identity a = Identity a

and the third is for:

data Two a b =
  Two a b

My answers:

For the first, I changed the instance expression to

instance Semigroup Trivial where
  _ <> _ = Trivial

and it works.

I tried the following code but not work for the second:

newtype Identity a = Identity a

instance (Semigroup a) => Semigroup (Identity a) where
  (Identity a1) <> (Identity a2) = Identity (a1 <> a2)

instance Arbitrary (Identity a) where
  arbitrary = return (Identity a)

type IdentityAssoc =
  (Identity a0) -> (Identity a1) -> (Identity a2) -> Bool

main :: IO ()
main =
  quickCheck (semigroupAssoc :: IdentityAssoc)

I find I do not understand what the quickTest should check here. I even tried:

import Data.NonEmpty

newtype Identity a = Identity a

instance (Semigroup a) => Semigroup (Identity a) where
  (Identity a1) <> (Identity a2) = Identity (a1 <> a2)

instance Arbitrary (Identity a) where
  arbitrary = return (Identity a)

type IdentityAssoc =
  (Identity (NonEmpty Int)) -> (Identity (NonEmpty Int)) -> (Identity (NonEmpty Int)) -> Bool

main :: IO ()
main =
  quickCheck (semigroupAssoc :: IdentityAssoc)

to make the parameterized types' parameters concrete. But it does not work either.

For the third, I do not know how to write them. But I think it is similar to the second one.

Can someone explain on these so that I can understand how to write the instance of parameterized Semigroups and their quickTest arbitrary?


Solution

  • This is wrong:

    instance Arbitrary (Identity a) where
      arbitrary = return (Identity a)
    

    a is not a value variable, it is a type variable. We need a value of type a to pass to the Identity constructor, not the a type itself.

    So we need something like

    instance Arbitrary a => Arbitrary (Identity a) where
      arbitrary = do
         x <- arbitrary         -- generate a value of type a
         return (Identity x)    -- turn it into a value of type (Identity a)
    

    (or, more concisely, arbitrary = Identity <$> arbitrary)

    Note how we have to require that a is a type for which we can generate random samples (adding Arbitrary a => after Instance). Otherwise, we can't use x <- arbitrary to generate a sample for a.

    Further:

    type IdentityAssoc =
      (Identity a0) -> (Identity a1) -> (Identity a2) -> Bool
    

    Here we can't refer to a1,a1,a2, since we haven't defined those types anywhere. We need to choose concrete types, like Int. Further, these three types must be the same type, since (<>) takes two values of the same type, and returns a value in that type.