۱۴ - ۷تمرین‌های فصل

حالا وقتش رسیده که تست‌های خودتون رو بنویسین. برای بیشترِ تمرین‌های کتاب میشه تست نوشت، اما اینکه از ‏‎hspec‎‏ استفاده کنین یا ‏‎QuickCheck‎‏ بستگی به این داره که می‌خواین چه چیزی رو تست کنین. ما خواستیم یه کم کار رو براتون ساده کنیم، به همین خاطر تو این تمرین‌ها گفتیم از کدوم استفاده کنین. ولی طبق معمول، شما رو تشویق می‌کنیم واسه خودتون آزمایش کنین.

تأییدِ اعداد به لغات

تمرینِ "اعداد به لغات" در فصلِ توابعِ بازگشتی خاطرتون هست؟ الان برای توابعی که اونجا نوشتین تست می‌نویسین.

module WordNumberTest where

import Test.Hspec
import WordNumber
  (digitToWord, digits, wordNumber)

main :: IO ()
main = hspec $ do
  describe "digitToWords" $ do
    it "returns zero for 0" $ do
      digitToWord 0 `shouldBe` "zero"
    it "returns one for 1" $ do
      print "???"

  describe "digits" $ do
    it "returns [1] for 1" $ do
      digits 1 `shouldBe` [1]
    it "returns [1, 0, 0] for 100" $ do
      print "???"

  describe "wordNumber" $ do
    it "one-zero-zero given 100" $ do
      wordNumber 100
        `shouldBe` "one-zero-zero"
    it "nine-zero-zero-one for 9001" $ do
      print "???"

بجای اون چاپ‌های علامت سؤال، تست‌های صحیح رو بنویسین. اگه تست‌های دیگه هم به ذهن‌تون میرسن اضافه کنین.

استفاده از ‏‎QuickCheck‎‏

مشخصه‌های چندتا حساب ِ ساده رو تست کنین:

۱.

-- برای تابع زیر
half x = x / 2

-- باید برقرار باشه (property) این مشخصه‎
halfIdentity = (*2) . half

۲.

import Data.List (sort)

-- رو بهش اعمال sort برای هر لیستی که
-- می‌کنین، این مشخصه باید برقرار باشه
listOrdered :: (Ord a) => [a] -> Bool
listOrdered xs =
  snd $ foldr go (Nothing, True) xs
  where go _ status@(_, False) = status
        go y (Nothing, t) = (Just y, t)
        go y (Just x, t) = (Just y, x >= y)

۳.

حالا جابجایی‌پذیری و شرکت‌پذیری ِ جمع رو تست می‌کنیم:

plusAssociative x y z =
  x + (y + z) == (x + y) + z
plusCommutative x y =
  x + y == y + x

۴.

حالا همون کار رو برای ضرب انجام بدین.

۵.

در یکی از فصل‌های اول کتاب، قوانینی برای روابطِ بینِ ‏‎quot‎‏ و ‏‎rem‎‏، و ‏‎div‎‏ و ‏‎mod‎‏ نشون دادیم. براشون تستِ ‏‎QuickCheck‎‏ بنویسین.

(quot x y)*y + (rem x y) == x

(div x y)*y + (mod x y) == x

۶.

آیا ‏‎(^)‎‏ شرکت‌پذیری داره؟ جابجایی‌پذیری چطور؟ ببینین ‏‎QuickCheck‎‏ چی میگه.

۷.

تست کنین که دوبار معکوس کردنِ یه لیست، معادلِ همانی برای لیست‌ه:

reverse . reverse == id

۸.

یه مشخصه برای تعریفِ ‏‎($)‎‏ بنویسین.

f $ a = f a

f . g = \x -> f (g x)

۹.

ببینین آیا این دوتا تابع‌ها برابرند یا نه:

foldr (:) == (++)

foldr (++) [] == concat

۱۰.

همم... واقعاً اینطوره؟

f n xs = length (take n xs) == n

۱۱.

بالاخره یه تمرین جذاب. اگه یادتون باشه یه بار ازتون خواستیم که تابع‌های ‏‎read‎‏ و ‏‎show‎‏ رو با هم ترکیب کنین تا یه "رفت‌وبرگشت" رو کامل کنین. خوب حالا می‌تونین تست کنین ببینین کار می‌کنه یا نه:

f x = (read (show x)) == x

شکست

ببینید چرا این مشخصه شکست می‌خوره.

-- برای تابع زیر
square x = x * x

-- چرا این مشخصه برقرار نیست؟
-- رو بررسی کنین sqrt تایپ
squareIdentity = square . sqrt

اگه با ممیز شناور و میزان دقت‌ش آشنا نیستین، یه کم راجع بهش تحقیق کنین.

تکرارشوندگی

تکرارشوندگی، خاصیت ِ بعضی از توابع‌ه که خروجیِ اونها بعد از اولین اعمال دیگه تغییری نمی‌کنه. اگه یکبار تابع رو اعمال کنین، یه جواب برمی‌گردونه، و اعمال ِ مجددِ همون تابع به جواب، دیگه هیچ وقت تغییرش نمیده. مثلاً مرتب کردنِ یه لیست: یکبار که مرتب ‌ِش کنین، بعد اعمال ِ همون تابعِ مرتب‌کننده روی لیستِ مرتب‌شده، دیگه تأثیری نداره.

به کمکِ ‏‎QuickCheck‎‏ و توابعِ کمکیِ زیر، تکرارشوندگی ِ توابعِ زیر رو نشون بدین:

twice f = f . f
fourTimes = twice . twice

۱.

f x =
  (capitalizeWord x
  == twice capitalizeWord x)
  &&
  (capitalizeWord x
  == fourTimes capitalizeWord x)

۲.

f' x =
  (sort x
  == twice sort x)
  &&
  (sort x
  == fourTimes sort x)

برای نوع‌داده‌ها ژنراتور تصادفی درست کنین

در طول فصل، نحوه‌ی ساختنِ ژنراتورهای ‏‎Gen‎‏ رو برای انواعِ نوع‌داده‌ها نشون دادیم. انقدر مطمئن هستیم که ازشون لذت بردین، که می‌خوایم خودتون برای چندتا نوع‌داده ِ جدید ژنراتور بسازین:

۱.

با احتمالِ مساوی برای هر کدوم:

data Fool =
    Fulse
  | Frue
  deriving (Eq, Show)

۲.

۲/۳ احتمال برای ‏‎Fulse‎‏، ۱/۳ احتمال برای ‏‎Frue‎‏.

data Fool =
    Fulse
  | Frue
  deriving (Eq, Show)

تستِ داربازی

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

fillInCharacter :: Puzzle -> Char -> Puzzle
fillInCharacter (Puzzle word
                 filledInSoFar s) c =
  Puzzle word newFilledInSoFar (c : s)
  where zipper guessed wordChar guessChar =
          if wordChar == guess
          then Just wordChar
          else guessChar
        newFilledInSoFar =
          let zd = (zipper c)
          in zipWith zd word filledInSoFar

و:

handleGuess :: Puzzle -> Char -> IO Puzzle
handleGuess puzzle guess = do
  putStrLn $ "Your guess was: " ++ [guess]
  case (charInWord puzzle guess
      , alreadyGuess puzzle guess) of
   (_, True) -> do
     putStrLn "You already guess that\
               \ character, pick \
               \ something else!"
     return puzzle
   (True, _) -> do
     putStrLn "This character was in the\
              \ word, filling in the word\
              \ accordingly"
     return puzzle
   (False, _) -> do
     putStrLn "This character wasn't in\
              \ the word, try again."
     return (fillInCharacter puzzle guess)

کاری که قراره انجام بدن رو به خاطر بیارین و با تست مطمئن بشین که اون کار رو انجام میدن.

تأییدِ رمزنگارها

به عنوان تمرین آخر، برای رمزنگارهای سزار و ویژنر، مشخصههای ‏‎QuickCheck‎‏ ای بنویسین که مساوی بودنِ یه متن رو بعد از رمزنگاری و رمزشکنی بررسی کنن.