Home > Net >  Using choose in frequency Haskell QuickCheck
Using choose in frequency Haskell QuickCheck

Time:05-09

So I have the code below and I am trying to make it an instance of Arbitrary:

data MyData = I Int | B Bool

instance Arbitrary MyData where
  arbitrary = do {
     frequency [(1, return (I 1)),
                (1, return (choose((B True), (B False))))]
  }

With this however I get (understandable) the error:

Couldn't match type ‘Gen MyData’ with ‘MyData’
      Expected type: Gen MyData
        Actual type: Gen (Gen MyData)

How can I accomplish to implement this? Also instead of (I 1) I would like to return I with a random Int. However using the arbitrary function instead of 1 leads to the same error.

CodePudding user response:

Since you seem to want to distribute evenly between I and B constructors, a simpler solution would be to use oneof instead of frequency:

data MyData = I Int | B Bool deriving (Eq, Show)

instance Arbitrary MyData where
  arbitrary = oneof [genI, genB]
    where genI = fmap I arbitrary 
          genB = fmap B arbitrary

The genI and genB generators use the underlying Arbitrary instances of Int and Bool by mapping raw integers and Boolean values to the respective case constructors.

Here's a set of sample data:

> sample (arbitrary :: Gen MyData)
B False
B False
I 2
B False
I 1
I 7
B False
B False
B True
I 7
B False

As you can see, it also accomplishes to pick arbitrary integers.


The code in the OP has several problems. The first error message is that the return type is nested. One way to get around that is to remove the do notation. This, however, doesn't solve the problem.

Even if you reduce it to the following, it doesn't type-check:

instance Arbitrary MyData where
  arbitrary =
     frequency [(1, return (I 1)),
                (1, choose(B True, B False))]

This attempt produces the error:

Q72160684.hs:10:21: error:
    * No instance for (random-1.1:System.Random.Random MyData)
        arising from a use of `choose'
    * In the expression: choose (B True, B False)
      In the expression: (1, choose (B True, B False))
      In the first argument of `frequency', namely
        `[(1, return (I 1)), (1, choose (B True, B False))]'
   |
10 |                 (1, choose(B True, B False))]
   |                     ^^^^^^^^^^^^^^^^^^^^^^^

The choose method requires the input to be Random instances, which MyData isn't.


If you really want to use frequency rather than oneof, the easiest way may be to first get oneof to work, since you can view frequency as a generalisation of oneof.

First, to make the code a little more succinct, I've used <$> instead of fmap and then inlined both generators:

instance Arbitrary MyData where
  arbitrary = oneof [I <$> arbitrary, B <$> arbitrary]

Now replace oneof with frequency and change each generator to a weighted tuple:

instance Arbitrary MyData where
  arbitrary = frequency [(10, I <$> arbitrary), (1, B <$> arbitrary)]

Sampling from this instance illustrates that the distribution is now skewed:

> sample (arbitrary :: Gen MyData)
I 0
I (-2)
I (-4)
I (-1)
I 0
I 8
B True
I 1
I 3
I (-3)
I (-16)

There are 10 I values and only 1 B value.

CodePudding user response:

You can derive it with generic-random (since 1.5.0.0).

This uses a uniform distribution (like oneof from Mark Seemann's answer), derive via GenericArbitraryU:

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

import Test.QuickCheck
import GHC.Generics

import Generic.Random.DerivingVia

-- ghci> sample (arbitrary :: Gen MyData)
-- I 0
-- I 1
-- B True
-- I 6
-- I (-5)
-- I (-7)
-- B True
-- I (-10)
-- B True
-- B True
-- I (-9)
data MyData = I Int | B Bool
  deriving
  stock (Show, Generic)

  deriving Arbitrary
  via GenericArbitraryU MyData

If you want a weighted distribution (like frequency) derive via GenericArbitrary with a type-level list of numbers:

-- ghci> sample (arbitrary :: Gen MyData)
-- I 0
-- I (-2)
-- I 4
-- I 5
-- I 2
-- I 0
-- B False
-- I (-9)
-- I (-10)
-- I (-3)
-- I (-8)
data MyData = I Int | B Bool
  deriving
  stock (Show, Generic)

  deriving Arbitrary
  via GenericArbitrary '[10, 1] MyData
  • Related