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