我尝试跟随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’
如何让它忘记已定义的实例并使用我自己的实例?或者它根本不会那样工作(这很奇怪,因为教程采用了这种方法)?
答案 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包装器中的原因,即Sum
和Product
。
更进一步,你的陈述
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)