Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using choose in frequency Haskell QuickCheck

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.

like image 322
Max Podpera Avatar asked Aug 30 '25 16:08

Max Podpera


1 Answers

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.

like image 80
Mark Seemann Avatar answered Sep 02 '25 12:09

Mark Seemann