为什么 QuickCheck 在测试具有特定类型签名的 Functor 实例时需要很长时间?

问题描述

我正在经历美妙的 Haskell Book。在解决一些练习时,我运行了 QuickCheck 测试,运行时间相对较长,我不知道为什么。

我正在解决的练习在第 16 章 - 我需要为

编写一个 Functor 实例
data Parappa f g a = 
  DaWrappa (f a) (g a)

这是我的解决方案的 a link to the full code。我认为相关的部分是:

functorCompose' :: (Eq (f c),Functor f) 
                => Fun a b -> Fun b c -> f a -> Bool
functorCompose' fab gbc x =
  (fmap g (fmap f x)) == (fmap (g . f) x)
  where f = applyFun fab
        g = applyFun gbc

type ParappaComp = 
     Fun Integer String 
  -> Fun String [Bool] 
  -> Parappa [] Maybe Integer
  -- -> Parappa (Either Char) Maybe Integer
  -> Bool


main :: IO ()
main = do
  quickCheck (functorCompose' :: ParappaComp) 

当我在 REPL 中运行它时,大约需要 6 秒才能完成。如果我将 ParappaComp 更改为使用 Either Char 而不是 [](请参阅代码中的注释),它会立即完成,就像我在所有其他练习中看到的一样。

我怀疑 QuickCheck 可能使用了很长的列表,导致测试需要很长时间,但我对环境不够熟悉,无法调试或测试这个假设。

  1. 为什么这需要这么长时间?
  2. 我应该如何调试这个?

解决方法

我怀疑 QuickCheck 可能使用了很长的列表,导致测试需要很长时间,但我对环境不够熟悉,无法调试或测试这个假设。

我也不确定实际原因,但开始调试此问题的一种方法是使用 QuickCheck 中的 collect 函数来收集有关测试用例的统计信息。首先,您可以收集结果的大小。

  • 获取大小的一种简单方法是使用 length 函数,要求函子 fFoldable
  • 您需要为 Foldable 实现或派生 Parappa(在文件顶部添加 {-# LANGUAGE DeriveFoldable #-},将 deriving Foldable 添加到 Parappa)立>
  • 要使用collect,您需要将Bool推广到Property(在functorCompose'的签名和类型同义词ParappaComp中)
functorCompose' :: (Eq (f c),Functor f,Foldable f) 
               => Fun a b -> Fun b c -> f a -> Property
functorCompose' fab gbc x =
  collect (length x) $
    (fmap g (fmap f x)) == (fmap (g . f) x)
  where f = applyFun fab
        g = applyFun gbc

由此你可以看到,生成的列表长度的分布聚集在 20 左右,长尾高达 100。仅凭这一点似乎并不能解释缓慢,正如人们所期望的那样大小应该几乎是即时的。