问题描述
我正在经历美妙的 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 可能使用了很长的列表,导致测试需要很长时间,但我对环境不够熟悉,无法调试或测试这个假设。
- 为什么这需要这么长时间?
- 我应该如何调试这个?
解决方法
我怀疑 QuickCheck 可能使用了很长的列表,导致测试需要很长时间,但我对环境不够熟悉,无法调试或测试这个假设。
我也不确定实际原因,但开始调试此问题的一种方法是使用 QuickCheck 中的 collect
函数来收集有关测试用例的统计信息。首先,您可以收集结果的大小。
- 获取大小的一种简单方法是使用
length
函数,要求函子f
为Foldable
- 您需要为
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。仅凭这一点似乎并不能解释缓慢,正如人们所期望的那样大小应该几乎是即时的。