如何使我的类型成为 Arbitrary 的实例?

How can I make my type an instance of Arbitrary?

我有以下数据和函数

data Foo = A | B deriving (Show)

foolist :: Maybe Foo -> [Foo]
foolist Nothing  = [A]
foolist (Just x) = [x]

prop_foolist x = (length (foolist x)) == 1

当 运行 quickCheck prop_foolist 时,ghc 告诉我 Foo 需要是 Arbitrary 的一个实例。

No instance for (Arbitrary Foo) arising from a use of  ‘quickCheck’
In the expression: quickCheck prop_foolist
In an equation for ‘it’: it = quickCheck prop_foolist

我尝试了 data Foo = A | B deriving (Show, Arbitrary),但这导致

Can't make a derived instance of ‘Arbitrary Foo’:
  ‘Arbitrary’ is not a derivable class
  Try enabling DeriveAnyClass
In the data declaration for ‘Foo’

但是,我不知道如何启用 DeriveAnyClass。我只是想通过我的简单功能使用快速检查! x 的可能值是 Nothing、Just A 和 Just B。当然这应该可以测试?

有两种合理的方法:

重用现有实例

如果有另一个看起来相似的实例,您可以使用它Gen 类型是 FunctorApplicative 甚至 Monad 的实例,因此您可以轻松地从其他生成器构建生成器。这可能是编写 Arbitrary 个实例最重要的通用技术。大多数复杂实例将由一个或多个更简单的实例构建。

boolToFoo :: Bool -> Foo
boolToFoo False = A
boolToFoo True = B

instance Arbitrary Foo where
  arbitrary = boolToFoo <$> arbitrary

在这种情况下,Foo 不能以任何有意义的方式 "shrunk" 到子部分,因此 shrink 的默认简单实现可以正常工作。如果它是一种更有趣的类型,您可以使用

的一些类似物
  shrink = map boolToFoo . shrink . fooToBool

使用 Test.QuickCheck.Arbitrary and/or Test.QuickCheck.Gen

中可用的片段

在这种情况下,将各个部分拼凑起来非常容易:

import Test.QuickCheck.Arbitrary

data Foo = A | B
  deriving (Show,Enum,Bounded)
instance Arbitrary Foo where
  arbitrary = arbitraryBoundedEnum

如前所述,默认的 shrink 实现在这种情况下就可以了。在递归类型的情况下,您可能想要添加

{-# LANGUAGE DeriveGeneric #-}
import GHC.Generics (Generic)

然后为您的类型导出 Generic 并使用

instance Arbitrary ... where
  ...
  shrink = genericShrink

正如文档警告的那样,genericShrink 不遵守您可能希望施加的任何内部有效性条件,因此在某些情况下可能需要小心。


您问的是 DeriveAnyClass。如果你想要那个,你会添加

{-# LANGUAGE DeriveAnyClass #-}

到文件的顶部。但是你不想要那个。无论如何,你肯定不想在这里。它仅适用于 class 具有基于泛型的完整默认值的 es,通常使用 DefaultSignatures 扩展。在这种情况下,Arbitrary class 定义中没有 default arbitrary :: Generic a => Gen a 行,并且 arbitrary 是强制性的。因此,一旦 QuickCheck 尝试调用其 arbitrary 方法,DeriveAnyClass 生成的 Arbitrary 实例将产生 运行时错误