'Issues with QuickCheck involving a data type with a function as a constructor

wrote the code below and am getting some issues with it :

The error I am getting is : Data constructor not in scope: Int :: Int

If I eradicate the Numeric Int element from my array the code works perfectly fine, however Numeric Int is a constructor of the type Rank so it should also be included but I am unsure of how to include it without this error being produced.

Below is the code and apologises if this question is long-winded or silly, this is my first post on StackOverflow so any feedback on how this q was asked would also be greatly appreciated.

Any help would be immensely appreciated

import Test.QuickCheck
import Data.Data
import Data.Typeable


data Suit = Spades | Hearts | Clubs | Diamonds
              deriving Show

data Colour = Black | Red
                deriving Show

colour :: Suit -> Colour
colour Spades = Black
colour Hearts = Red
colour Diamonds = Red
colour Clubs = Black

data Rank = Numeric Int | Jack | Queen | King | Ace
              deriving Show

rankBeats :: Rank -> Rank -> Bool
rankBeats _ Ace = False
rankBeats Ace _ = True
rankBeats _ King = False
rankBeats King _ = True
rankBeats _ Queen = False
rankBeats Queen _ = True
rankBeats _ Jack = False
rankBeats Jack _ = True
rankBeats (Numeric m) (Numeric n) = m > n

prop_rankBeats :: Rank -> Rank -> Bool
prop_rankBeats a b = rankBeats a b || rankBeats b a

instance Arbitrary Rank where
  arbitrary = elements [Numeric Int,Jack, Queen, King, Ace]


Solution 1:[1]

Your Arbitrary instance for a Rank contains an Int:

instance Arbitrary Rank where
  --                      an Int ↓
  arbitrary = elements [Numeric Int, Jack, Queen, King, Ace]

But an Int is not a data constructor, but a type constructor. You can not use this.

What you can do is make a generator that looks like:

instance Arbitrary Rank where
  arbitrary = oneof ((Numeric <$> arbitrary) : map pure [Jack, Queen, King, Ace])

here the first item Numeric <$> arbitrary will use the Arbitrary instance of the Int type, and furthermore we use map pure [Jack, Queen, King, Ace] to transform these Ranks into Gen Ranks. The oneof :: [Gen a] -> Gen a will then each time pick a random generator from the list. oneof will pick the items with equal weight. We can for example use frequency to pick these with different weights:

{-# LANGUAGE TupleSections #-}

instance Arbitrary Rank where
  arbitrary = frequency ((9, Numeric <$> chooseInt (2,10)) : map ((1,) . pure) [Jack, Queen, King, Ace])

here this is more fair: 9 out of 13 times, it will pick a Numeric, and we use chooseInt (2, 10) such that we only generate Ints between 2 and 10.

Solution 2:[2]

The code from Willem can also be generically derived.

Using the package generic-random:

{-# Language DataKinds     #-}
{-# Language DeriveGeneric #-}
{-# Language DerivingVia   #-}

import GHC.Generics
import Generic.Random.DerivingVia
import Test.QuickCheck

-- ghci> :set -XTypeApplications
-- ghci> sample @Rank arbitrary
-- King
-- Queen
-- King
-- Jack
-- Numeric (-4)
-- Numeric (-9)
-- Jack
-- Jack
-- Queen
-- Ace
-- Queen
data Rank = Numeric Int | Jack | Queen | King | Ace
  deriving
  stock (Show, Generic)

  deriving Arbitrary
  via GenericArbitraryU Rank

Adding weights (like frequency):

-- ghci> sample @Rank arbitrary
-- Numeric 0
-- Queen
-- Numeric (-4)
-- Numeric (-6)
-- Numeric 4
-- Numeric 3
-- Numeric 9
-- Numeric (-3)
-- Numeric 12
-- Jack
-- Numeric 20
data Rank = Numeric Int | Jack | Queen | King | Ace
  deriving
  stock (Show, Generic)

  deriving Arbitrary
  via GenericArbitrary '[9, 1, 1, 1, 1] Rank

We can even implement the choose (2, 10) with generic-override!

{-# Language InstanceSigs             #-}
{-# Language ScopedTypeVariables      #-}
{-# Language StandaloneKindSignatures #-}
{-# Language TypeApplications         #-}
{-# Language TypeOperators            #-}

..

import Data.Kind
import Data.Proxy
import GHC.TypeLits
import System.Random

-- ghci> sample @Rank arbitrary
-- King
-- Numeric 10
-- Queen
-- Jack
-- Jack
-- King
-- Numeric 8
-- Numeric 7
-- Numeric 4
-- Numeric 4
-- Numeric 6
data Rank = Numeric Int | Jack | Queen | King | Ace
  deriving
  stock (Show, Generic)

  deriving Arbitrary
  via GenericArbitrary '[9, 1, 1, 1, 1]
        (Override Rank '[Int `With` Choose 2 10])

Using

type    Choose :: Nat -> Nat -> Type -> Type
newtype Choose n m a = Choose a

instance (KnownNat n, KnownNat m, Num a, Random a) => Arbitrary (Choose n m a) where
  arbitrary :: Gen (Choose n m a)
  arbitrary = Choose <$> choose (fromInteger (natVal @n Proxy), fromInteger (natVal @m Proxy))

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Iceland_jack