簡體   English   中英

List的應用實例在使用QuickCheck / Checkers的組合法測試中永遠運行

[英]Applicative instance of List runs forever in composition law test with QuickCheck/Checkers

我想使用我自定義的列表實現列表的常規應用實例:

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))

兩者都給出了相同的結果,所以為什么組成法永遠不會結束讓我難過。 這可能是跳棋圖書館的一個問題?

我首先想到的是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] ):

時間圖(x是模數,y是秒)

快速回歸發現5.72238 - 2.8458 x + 0.365263 x^2 當我運行跟蹤時, n繼續增加。 雖然我不確定測試是如何運行的,但如果每次測試增加n ,那么n將達到500

公式並不公平,但讓我們假設這是一個不錯的界限。 (我認為應該是因為算法是O(n^2) 。)

然后在我的機器上運行所有測試大約需要25個小時。

PS因為所有的測試都通過n合理界限而我找不到錯誤,我認為你的代碼是正確的。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM