使用选择频率 Haskell QuickCheck

Using choose in frequency Haskell QuickCheck

所以我有下面的代码,我试图让它成为 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))))]
  }

但是我得到了(可以理解的)错误:

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

我怎样才能实现这个?另外,我想 return I 随机 Int 而不是 (I 1)。但是,使用 arbitrary 函数而不是 1 会导致相同的错误。

由于您似乎希望在 IB 构造函数之间均匀分布,因此更简单的解决方案是使用 oneof 而不是 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

genIgenB 生成器通过将原始整数和布尔值映射到各自的情况来使用 IntBool 的基础 Arbitrary 实例构造函数。

这是一组示例数据:

> 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

正如你所看到的,它也完成了选择任意整数。


OP 中的代码有几个问题。第一条错误消息是 return 类型是嵌套的。解决这个问题的一种方法是删除 do 符号。然而,这并不能解决问题。

即使你把它减少到下面这样,它也不会type-check:

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

此尝试产生错误:

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))]
   |                     ^^^^^^^^^^^^^^^^^^^^^^^

choose 方法要求输入是 Random 个实例,而 MyData 不是。


如果您真的想使用 frequency 而不是 oneof,最简单的方法可能是首先让 oneof 起作用,因为您可以将 frequency 视为oneof.

的概括

首先,为了使代码更加简洁,我使用了 <$> 而不是 fmap,然后内联了两个生成器:

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

现在将 oneof 替换为 frequency 并将每个生成器更改为加权元组:

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

从这个实例中抽样说明分布现在是倾斜的:

> 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)

有 10 个 I 个值,只有 1 个 B 个值。

你可以用 generic-random (since 1.5.0.0).

导出它

通过 GenericArbitraryU 推导:提供均匀分布(如来自 Mark Seemann 的回答的 oneof):

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

import Test.QuickCheck
import GHC.Generics

import Generic.Random.DerivingVia

-- ghci> :set -XTypeApplications
-- ghci> sample @MyData arbitrary
-- 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

通过 GenericArbitrary 推导:给出由 type-level 数字列表指定的加权分布。它们表示每个构造函数的频率(如 frequency):

-- ghci> sample @MyData arbitrary
-- 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