如何让我的类型成为任意的实例?

时间:2016-02-21 22:06:01

标签: haskell generics testing quickcheck

我有以下数据和功能

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需要是一个任意的实例。

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.当然这应该可以测试吗?

1 个答案:

答案 0 :(得分:5)

有两种合理的方法:

重用现有实例

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

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

instance Arbitrary Foo where
  arbitrary = boolToFoo <$> arbitrary

在这种情况下,Foo无法&#34;缩小&#34;以任何有意义的方式对子部分,因此shrink的默认普通实现将正常工作。如果它是一个更有趣的类型,你可以使用一些类似的

  shrink = map boolToFoo . shrink . fooToBool

使用Test.QuickCheck.Arbitrary和/或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 #-}

到文件的顶部。但你不想要那个。无论如何,你当然不想要它。它仅适用于具有基于泛型的完整默认值的类,通常使用DefaultSignatures扩展名。在这种情况下,default arbitrary :: Generic a => Gen a类定义中没有Arbitrary行,arbitrary是必需的。因此,只要QuickCheck尝试调用其Arbitrary方法,DeriveAnyClass生成的arbitrary实例就会产生运行时错误