۱۶ - ۹تست کردن نمونه‌های Functor با QuickCheck

با قوانینِ ‏‎Functor‎‏ آشنا شدیم:

fmap id      = id
fmap (p . q) = (fmap p) . (fmap g)

میشه اونها رو اینطوری به مشخصه‌های ‏‎QuickCheck‎‏ تبدیل کنیم:

functorIdentity :: (Functor f, Eq (f a)) =>
                        f a
                     -> Bool
functorIdentity f =
  fmap id f == f

functorCompose :: (Eq (f c), Functor f) =>
                       (a -> b)
                    -> (b -> c) 
                    -> f a
                    -> Bool
functorCompose f g x =
  (fmap g (fmap f x)) == (fmap (g . f) x)

با تأمین تایپ‌های معیّن، میشه این تست‌ها رو انجام بدیم:

Prelude> :{
*Main| let f :: [Int] -> Bool
*Main|     f x = functorIdentity x
*Main| :}
Prelude> quickCheck f
+++ OK, passed 100 tests.

Prelude> let c = functorCompose (+1) (*2)
Prelude> let li x = c (x :: [Int])
Prelude> quickCheck li
+++ OK, passed 100 tests.

خیلی عالی.

کاری کنیم ‏‎QuickCheck‎‏ تابع هم ایجاد کنه

QuickCheck قابلیتِ ایجاد ِ توابع هم داره. تایپکلاسی به اسمِ ‏‎CoArbitrary‎‏ داره که تایپِ آرگومان تابع رو پوشش میده؛ و در مقابل، تایپکلاس ‏‎Arbitrary‎‏ (که بی‌ربط هم نیست) برای تایپِ نتیجه‌ی تابع استفاده میشه. اگه می‌خواین بیشتر بدونین، یه نگاه به ماژول ِ ‏‎Function‎‏ از کتابخونه ِ ‏‎QuickCheck‎‏ بندازین تا ببینید چطور از نوع‌داده‌ای که ساختِ تابع رو نشون میده، توابع ایجاد میشن.

{-# LANGUAGE ViewPatterns #-}

import Test.QuickCheck
import Test.QuickCheck.Function

functorCompose' :: (Eq (f c), Functor f) =>
                   f a
                -> Fun a b
                -> Fun b c
                -> Bool
functorCompose' x (Fun _ f) (Fun _ g) =
  (fmap (g . f) x) == (fmap g . fmap f $ x)

اینجا چندتا کار انجام دادیم. یکی اینکه باید یه ماژول ِ جدید از ‏‎QuickCheck‎‏ وارد می‌کردیم. دیگه اینکه روی مقدارِ ‏‎Fun‎‏ که از ‏‎QuickCheck‎‏ خواستیم ایجاد کنه تطبیق الگو انجام دادیم. تایپ ‏‎Fun‎‏ در حقیقت حاصلضرب ِ یه جور تابع با تایپی عجیب (مختصِ خودِ ‏‎QuickCheck‎‏) و یه تابعِ معمولیِ هسکل‌ه. ما فقط با بخشِ دوم‌ش کار داریم، یعنی اون تابعِ معمولی، پس با تطبیق الگو می‌کشیم‌ش بیرون.

Prelude> type IntToInt = Fun Int Int
Prelude> :{
*Main| type IntFC =
*Main|          [Int]
*Main|       -> IntToInt
*Main|       -> IntToInt
*Main|       -> Bool
*Main| :}
Prelude> let fc' = functorCompose'
Prelude> quickCheck (fc' :: IntFC)
+++ OK, passed 100 tests.

حواستون باشه که اون مقادیرِ ‏‎Fun‎‏ رو نمیشه چاپ کرد، پس ‏‎verboseCheck‎‏ کار نمی‌کنه.