۱۷ - ۷می‌دونستین وقتش میرسه

وقتِ تست مشخصه‌ای برای قوانینِ اپلیکتیو! تا الان باید نحوه‌ی نوشتنِ مشخصه برای قوانین رو یاد گرفته باشین، پس این دفعه از یه کتابخونه استفاده می‌کنیم. کونال الیوت یه کتابخونه ِ خوب روی Hackage و Github داره به اسمِ checkers، که امکانات خوبی برای ‏‎QuickCheck‎‏ تأمین می‌کنه.

بعد از نصبِ checkers، برای دوره‌ی کارهای قبلی، می‌تونیم از مشخصه‌های از پیش تعریف شده برای ‏‎Monoid‎‏‌ها و ‏‎Functor‎‏‌ها استفاده کنیم.

module BadMonoid where

import Data.Monoid
import Test.QuickCheck
import Test.QuickCheck.Checkers
import Test.QuickCheck.Classes

data Bull =
    Fools
  | Twoo
  deriving (Eq, Show)

instance Arbitrary Bull where
  arbitrary =
    frequency [ (1, return Fools)
              , (1, return Twoo) ]

instance Monoid Bull where
  mempty = Fools
  mappend _ _ = Fools

instance EqProp Bull where (=-=) = eq

map :: IO ()
map = quickBatch (monoid Twoo)

چندتا از فرق‌ها رو اشاره می‌کنیم. یکی اینکه خودمون نباید قوانینِ ‏‎Monoid‎‏ رو به عنوانِ مشخصه‌های ‏‎QuickCheck‎‏ بنویسیم؛ همه‌شون توی یه ‏‎TestBatch‎‏ به اسمِ ‏‎monoid‎‏ تعبیه شدن. تفاوت دیگه اینکه باید برای نوع‌داده ِ خودمون یه ‏‎EqProp‎‏ تعریف کنیم. البته کارِ ساده‌ایه چون checkers تابعی به اسمِ ‏‎eq‎‏ رو صادر می‌کنه، که با استفاده از نمونه ِ ‏‎Eq‎‏ ِ تایپ، کلِ کار رو انجام میده. نهایتاً هم یکی از مقادیرِ تایپ‌مون رو به ‏‎monoid‎‏ دادیم تا بدونه از کدوم نمونه ِ ‏‎Arbitrary‎‏ برای مقادیرِ تصادفی استفاده کنه– دقت کنین که از این مقدار برای هیچ کاری استفاده نمی‌کنه.

با اجرای ‏‎main‎‏ نتیجه رو می‌بینیم:

Prelude> main

monoid:
  left  identity:
   *** Failed! Falsifiable (after 1 test):
Twoo
  right identity:
   *** Failed! Falsifiable (after 2 tests):
Twoo
  associativity:  +++ OK, passed 500 tests.

همونطور که انتظار داشتیم، نقضِ همانی ِ چپ و راست در ‏‎Bull‎‏ رو تشخیص داد. حالا یه نمونه ِ ‏‎Applicative‎‏ ِ از پیش تعریف شده، مثل لیست یا ‏‎Maybe‎‏ رو تست می‌کنیم. تایپِ ‏‎TestBatch‎‏ که باهاش نمونه‌های ‏‎Applicative‎‏ رو تست می‌کنه یه کم ناخوشاینده...

applicative
:: ( Show a, Show (m a), Show (m (a -> b))
   , Show (m (b -> c)), Applicative m
   , CoArbitrary a, EqProp (m a)
   , EqProp (m b), EqProp (m c)
   , Arbitrary a, Arbitrary b
   , Arbitrary (m a)
   , Arbitrary (m (a -> b))
   , Arbitrary (m (b -> c))
   => m (a, b, c) -> TestBatch

اول یه کلک برای کار با تابع‌هایی مثل این. می‌دونیم که برای ساختار ِ ‏‎Applicative‎‏، و توابع (‏‎a -> b‎‏ و ‏‎b -> c‎‏) ِ داخل اون ساختار، نمونه ِ ‏‎Arbitrary‎‏ می‌خواد، و اینکه نمونه‌های ‏‎EqProp‎‏ هم می‌خواد. مشکلی نیست؛ میشه نادیده‌ بگیریم‌شون.

      m (a, b, c) -> TestBatch

چیزی که برامون مهمه، ‏‎m (a, b, c) -> TestBatch‎‏ ِه. میشه یه مقدار بهش بدیم که ساختار و سه تایپِ دیگه رو تعیین کنه (که می‌تونن تایپ‌های متفاوت داشته باشن، ولی نه لزوماً). حتی میشه یه مقدار تهی که تایپ‌ش رو تعیین کردیم بهش بدیم، تا از روی تایپ‌ها بدونه چه مقادیرِ تصادفی‌ای برای تستِ نمونه ِ ‏‎Applicative‎‏ ایجاد کنه.

Prelude> let xs = [("b", "w", 1)]
Prelude> quickBatch $ applicative xs

applicative:
  identity:     +++ OK, passed 500 tests.
  composition:  +++ OK, passed 500 tests.
  homomorphism: +++ OK, passed 500 tests.
  interchange:  +++ OK, passed 500 tests.
  functor:      +++ OK, passed 500 tests.

دقت کنین که ‏‎1 :: Num a => a‎‏ رو به تایپِ پیش‌فرض ِش تعیین کرد تا تایپِ مبهم نداشته باشه. اگه این کار رو می‌خواستیم خارج از GHCi انجام بدیم، خودمون باید تعیین‌ش می‌کردیم. در مثال زیر با استفاده از یه مقدارِ تهی تایپکلاس‌های موردنظر رو خبر می‌کنیم:

Prelude> type SSI = (String, String, Int)
Prelude> :{
*Main| let trigger :: [SSI]
*Main|     trigger = undefined
*Main| :}
Prelude> quickBatch (applicative trigger) 

applicative:
  identity:     +++ OK, passed 500 tests.
  composition:  +++ OK, passed 500 tests.
  homomorphism: +++ OK, passed 500 tests.
  interchange:  +++ OK, passed 500 tests.
  functor:      +++ OK, passed 500 tests.

تکرار می‌کنیم، مقداری که بهش دادین رو اصلاً محاسبه نمی‌کنه. ازش فقط برای تعیینِ تایپ‌ها استفاده می‌کنه.