不能为`Char`定义自定义`Arbitrary`实例,因为它已经存在

时间:2015-04-11 12:25:16

标签: haskell quickcheck

我尝试跟随Introduction to Quickcheck并想测试我的函数,它接受包含数字的字符串。为此,我为Arbitrary定义了Char个实例:

instance Arbitrary Char where
    arbitrary = choose ('0', '9')

但是ghc抱怨说:

A.hs:16:10:
Duplicate instance declarations:
  instance Arbitrary Char -- Defined at A.hs:16:10
  instance [overlap ok] [safe] Arbitrary Char
    -- Defined in ‘Test.QuickCheck.Arbitrary’

如何让它忘记已定义的实例并使用我自己的实例?或者它根本不会那样工作(这很奇怪,因为教程采用了这种方法)?

2 个答案:

答案 0 :(得分:11)

正如@ carsten-könig所建议的那样,解决方案是为newtype制作一个Char包装器。这不是一种解决方法,而是一种正确且非常好的方法来逃避与孤儿实例相关的整类问题(在另一个模块中定义的类型类的实例),详细了解这些问题here。 / p>

此外,当存在多种具有不同行为的可能实例时,这种方法被广泛使用。

例如,考虑Data.Monoid中定义的Monoid类型类:

class Monoid a where
    mempty  :: a           -- ^ Identity of 'mappend'
    mappend :: a -> a -> a -- ^ An associative operation

正如您可能已经知道的,Monoid是一种可以相互附加的值(使用mappend),并且存在满足规则{mempty的'identity'值mappend mempty a == a 1}}(将标识附加到值a会产生a)。有一个明显的Monoid for Lists实例:

class Monoid [a] where
    mempty  = []
    mappend = (++)

定义Int也很容易。实际上,加法运算的整数形成了正确的幺半群。

class Monoid Int where
    mempty  = 0
    mappend = (+)

但这是整数唯一可能的幺半群吗?当然不是,整数乘法会形成另一个合适的幺半群:

class Monoid Int where
    mempty  = 1
    mappend = (*)

两个实例都是正确的,但现在我们遇到了一个问题:如果你试图评估1 `mappend` 2,就无法确定必须使用哪个实例。

这就是为什么Data.Monoid将数字的实例包装到newtype包装器中的原因,即SumProduct

更进一步,你的陈述

instance Arbitrary Char where
    arbitrary = choose ('0', '9')

可能会非常混乱。它说“我是一个任意角色”,但只产生数字字符。在我看来,这会好得多:

newtype DigitChar = DigitChar Char deriving (Eq, Show)

instance Arbitrary DigitChar where
    arbitrary = fmap DigitChar (choose ('0', '9'))

一块蛋糕。您可以更进一步隐藏DigitChar构造函数,提供digitChar'智能构造函数',这将不允许创建实际上不是数字的DigitChar

截至你的问题“你知道为什么这不是教程采用的方法吗?”,我认为原因很简单,教程似乎写于2006年,而those days快速检查根本没有为Arbitrary定义Char个实例。因此,教程中的代码在当时完全有效。

答案 1 :(得分:0)

您无需为ad-hoc测试输入生成创建新的Arbitrary实例。您可以使用QuickCheck's forAll combinator明确选择Gen a到函数:

digit :: Gen Char
digit = choose ('0', '9)

prop_myFun = forAll digit $ \x -> isAwesome (myFun x)