۱۷ - ۷میدونستین وقتش میرسه
وقتِ تست مشخصهای برای قوانینِ اپلیکتیو! تا الان باید نحوهی نوشتنِ مشخصه برای قوانین رو یاد گرفته باشین، پس این دفعه از یه کتابخونه استفاده میکنیم. کونال الیوت یه کتابخونه ِ خوب روی 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.تکرار میکنیم، مقداری که بهش دادین رو اصلاً محاسبه نمیکنه. ازش فقط برای تعیینِ تایپها استفاده میکنه.