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