List的应用实例在使用QuickCheck / Checkers的组合法测试中永远运行

时间:2016-05-19 18:27:10

标签: list haskell applicative

我想使用我自定义的列表实现列表的常规应用实例:

import Control.Monad

import Test.QuickCheck
import Test.QuickCheck.Checkers
import Test.QuickCheck.Classes

data List a =
  Nil
  | Cons a (List a)
  deriving (Eq, Ord, Show)


instance Functor List where
  fmap f (Cons x xs) = Cons (f x) (fmap f xs)
  fmap f Nil = Nil


instance Applicative List where
  pure x = Cons x Nil
  (<*>) Nil _ = Nil
  (<*>) _ Nil = Nil
  (<*>) (Cons f fs) xs = (+++) (fmap f xs) (fs <*> xs)

(+++) :: List a -> List a -> List a
(+++) (Cons x Nil) ys = Cons x ys
(+++) (Cons x xs) ys = Cons x xs'
  where xs' = (+++) xs ys

instance Arbitrary a => Arbitrary (List a)  where
  arbitrary = sized go
    where go 0 = pure Nil
          go n = do
            xs <- go (n - 1)
            x  <- arbitrary
            return (Cons x xs)

instance (Eq a) => EqProp (List a) where
  (=-=) = eq

main = do
  let trigger = undefined :: List (Int, String, Int)
  quickBatch $ applicative trigger

我的代码通过了Checkers中的所有应用测试,除了一个组成法。测试组成法时没有错误,它永远不会完成。

我的代码是否以某种我无法看到的方式永远复发,或者仅仅是测试组合法的速度很慢?

这是我在Checkers执行期间控制-c时得到的错误消息:

applicative:
  identity:     +++ OK, passed 500 tests.
  composition:  *** Failed! Exception: 'user interrupt' (after 66 tests): 
Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> Nil))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))
Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> (Cons <function> Nil))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))
Cons (-61) (Cons (-24) (Cons 56 (Cons (-10) (Cons 28 (Cons 5 (Cons (-5) (Cons 33 (Cons 18 (Cons 47 (Cons 43 (Cons 43 (Cons (-58) (Cons 35 (Cons (-52) (Cons (-52) (Cons (-41) (Cons 3 (Cons (-7) (Cons (-53) (Cons (-22) (Cons (-20) (Cons (-12) (Cons 46 (Cons (-53) (Cons 35 (Cons (-31) (Cons (-10) (Cons 43 (Cons (-16) (Cons 47 (Cons 53 (Cons 22 (Cons 8 (Cons 1 (Cons (-64) (Cons (-39) (Cons (-57) (Cons 34 (Cons (-31) (Cons 20 (Cons (-39) (Cons (-47) (Cons (-59) (Cons 15 (Cons (-42) (Cons (-31) (Cons 4 (Cons (-62) (Cons (-14) (Cons (-24) (Cons 47 (Cons 42 (Cons 61 (Cons 29 (Cons (-25) (Cons 30 (Cons (-20) (Cons 16 (Cons (-30) (Cons (-38) (Cons (-7) (Cons 16 (Cons 19 (Cons 20 Nil))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))
  homomorphism: +++ OK, passed 500 tests.
  interchange:  +++ OK, passed 500 tests.
  functor:      +++ OK, passed 500 tests.

如果其中一个函数很慢,我猜它是(+++),但我不知道GHC如何很好地执行代码来理解原因。

更新

组成法是:

pure (.) <*> u <*> v <*> w = u <*> (v <*> w)

我可以使用我的代码显示简单示例:

Cons (+1) Nil <*> (Cons (*2) Nil <*> Cons 1 (Cons 2 (Cons 3 Nil)))

pure (.) <*> Cons (+1) Nil <*> Cons (*2) Nil <*> Cons 1 (Cons 2 (Cons 3 Nil))

两者都给出了相同的结果,所以为什么组成法永远不会结束让我难过。这可能是checkers库的问题吗?

1 个答案:

答案 0 :(得分:2)

我的第一个想法是go得到了一个负面的论证和循环。但是,当修改它以使用trace并在n < 0时抛出错误时,我发现它更简单:你的代码真的很慢。

这里是我修改的部分(go'用于跟踪,但我将其短路进行基准测试):

import Debug.Trace

(+++) :: List a -> List a -> List a
{-# INLINE (+++) #-}
(+++) (Cons x Nil) ys = Cons x ys
(+++) (Cons x xs) ys = Cons x xs'
  where xs' = (+++) xs ys

maxListSize = 10

instance Arbitrary a => Arbitrary (List a)  where
  arbitrary = sized go''
    where
      go'' n = go' $ mod n maxListSize
      go' n = if n < 0 then error ("bad n:" ++ show n) else trace (show n ++ " , ") $ go n
      go 0 = pure Nil
      go n = do
        xs <- go' (n - 1)
        x  <- arbitrary
        return (Cons x xs)

检查跟踪是否存在某种无限循环,我发现事情从未停止过,n不断减少然后弹出以备下一次测试。当它放慢速度时,它只需要进行单次测试。请记住,您正尝试每次测试运行500次。

我的基准测试并不严谨,但我得到的是(x是模数,范围[1..18]):

Time Plot (x is modulus, y is seconds)

快速回归找到5.72238 - 2.8458 x + 0.365263 x^2。当我运行跟踪时,n不断增加。虽然我不确定如何运行测试,但如果每次测试增加n,那么n将达到500

这个公式并不公平,但让我们假设它是一个不错的界限。 (我认为应该是因为算法是O(n^2)。)

然后在我的机器上运行所有测试大约需要25个小时。

P.S。由于所有测试都通过了n上的合理范围,我无法找到错误,我认为您的代码是正确的。