QuickCheck如何检测数据类型



如果我们定义这样的函数

fun :: Int -> Property

然后运行

quickCheck fun

quickCheck开始生成Int类型的随机数据。问题是quickCheck如何检测fun的参数数据类型是Int而不是任何其他数据类型?如果我把这个问题说得更一般,我应该问,如果我们有一个像这样的叫做fun的函数

fun :: datatype_1 -> datatype_2 -> ... -> datatype_n -> Property

quickCheck如何检测每个数据类型_1、数据类型_2。。。和数据类型n?以及它如何检测函数fun需要多少个参数?

大致上,这就是类型类的工作方式。可以申报

class C a where
foo :: a -> Bool

然后

instance C (Int -> Bool) where
foo f = f 42
instance C (String -> Bool) where
foo f = f "hello"
instance C (String -> [Int]) where
foo f = sum (f "hello") > 42

等等

这具有明显的效果,使foo"检测"其参数f的类型并相应地采取行动。实际上,发生的是Haskell执行类型推断,在此过程中,在编译时选择适当的实例。在运行时,不会发生"类型检测";事实上,类型在编译后被擦除,并且在运行时没有可用的类型信息,因此不可能检测出f属于哪种类型。

当然,实际的QuickCheck机制要复杂得多。为了处理具有任意数量参数的函数,可以说,使用了一组"递归"instances,在每次"递归调用"中处理每个参数。这是一个相当棘手的技术,也用于printf和其他"变差"函数。如果你不熟悉类型课程,我不建议你从这样一个复杂的技巧开始学习。

参与有点晚,但这是您在当前实现中要查找的实例。

instance (Arbitrary a, Show a, Testable prop) => Testable (a -> prop) where
property f =
propertyForAllShrinkShow arbitrary shrink (return . show) f
propertyForAllShrinkShow gen shr shw f =
-- gen :: Gen b, shr :: b -> [b], f :: b -> a -> prop
-- Idea: Generate and shrink (b, a) as a pair
propertyForAllShrinkShow
(liftM2 (,) gen arbitrary)
(liftShrink2 shr shrink)
((x, y) -> shw x ++ [show y])
(uncurry f)

正如@chi正确指出的那样,这里发生了递归。递归调用是propertyForAllShrinkShow调用propertyForAllShrinkShow,通过调用uncurry,形式为a -> b -> c -> Bool的属性将变为(a, b) -> c -> Bool。由于(a, b)是有效的任意,因为存在Arbitrary (a, b)instance,所以Testable的相同实例将再次运行,其中propc -> Bool。然后,与((a, b), c相同的内容将再次运行,并且prop将仅为Bool。此时,TestableBool实例启动,它使用默认的propertyForAllShrinkShow,创建f x的实际应用程序。因此,另一种说法是,快速检查同时生成所有值作为任意元组,并在Testableinstance中使用递归来构建元组。

最新更新