关于haskell:QuickCheck如何检测数据类型?

How does QuickCheck detect datatypes?

如果我们定义这样的函数

1
fun :: Int -> Property

然后运行

1
quickCheck fun

quickCheck开始生成Int类型的随机数据。问题是quickCheck如何检测到fun的参数数据类型是Int而不是其他任何数据类型?如果我让这个问题更笼统,我应该问我们是否有一个名为fun的函数,像这样

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

quickCheck如何检测每个单独的数据类型_1,数据类型_2,...和数据类型_n的类型?以及它如何检测功能fun需要多少个参数?


大致上,这就是类型类的工作方式。一个人可以声明

1
2
class C a where
   foo :: a -> Bool

然后

1
2
3
4
5
6
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机制要复杂得多。为了处理带有任意数量参数的函数,可以使用一组"递归" instance,可以说每个"递归调用"都处理每个参数。这是一个相当棘手的技术,也用于printf和其他"可变"函数中。如果您不熟悉类型类,建议您不要从这种复杂的技巧开始学习它们。


晚了一点,但这是您在当前实现中正在寻找的实例。

1
2
3
4
5
6
7
8
9
10
11
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。此时,将使用默认的propertyForAllShrinkShow启动TestableBool实例,该实例将创建f x的实际应用程序。因此,另一种说法是,快速检查会同时生成一个任意元组的所有值,并在Testableinstance中使用递归来构造该元组。