۷ - ۵بیانیه‌های case

مشابه ‏‎if-then-else‎‏، بیانیه‌های case هم برای تعیین خروجی‌های متفاوتِ توابع، بر اساس ورودی‌های متفاوت به کار میرن. بیانیه‌های case با هر نوع‌داده‌ای که داده‌سازهای مرئی (م. در گستره) داشته باشه قابل استفاده‌اند. نوع‌داده ِ ‏‎Bool‎‏ رو که بررسی کنیم:

data Bool = False | True
--   [1]     [2]     [3]

۱.

نوع‌ساز، که فقط در تایپ سیگنچرها استفاده میشه، و در کُدهای سطح جمله‌ای مثل بیانیه‌های case استفاده نمیشه.

۲.

داده‌ساز برای مقداری از تایپِ ‏‎Bool‎‏ به اسمِ ‏‎False‎‏ – میشه روی این تطبیق داد.

۳.

داده‌ساز برای مقداری از تایپ ‏‎Bool‎‏ به اسم ‏‎True‎‏ – میشه روی این هم تطبیق داد.

هر وقت روی یه تایپِ جمع مثل ‏‎Bool‎‏ تطبیقِ case یا تطبیقِ الگو انجام میدیم، باید برای هر داده‌ساز تعریفِ جدا داشته باشیم، یا یه تعریفِ پیش‌فرض برای انطباق با همه‌ی داده‌سازها داشته باشیم. در واقع همیشه باید هر دو حالت رو در نظر بگیریم، یا از تابعی که هر دو حالت رو در نظر می‌گیره استفاده کنیم؛ در غیر اینصورت یه تابعِ ناقص نوشتیم که ممکنه خطای زمان اجرا بده. واقعاً به ندرت چنین کاری توجیه داره؛ پس توابعی بنویسین که همه‌ی ورودی‌های ممکن رو در نظر می‌گیرن!

با یه بیانیه‌ی ‏‎if-then-else‎‏ که در فصلِ قبل دیدیم شروع می‌کنیم:

If x + 1 == 1 then "AWESOME" else "wut"

میشه این رو با بیانیه‌ی case، با انطباق روی داده‌سازهای ‏‎Bool‎‏ بازنویسی کنیم:

funcZ x =
    case x + 1 == 1 of
       True -> "AWESOME"
       False -> "wut"

با اینکه گرامر خیلی تغییر کرده، ولی جواب هَمونه. حتماً تو REPL بارگذاری و امتحان‌ش کنین.

می‌تونیم تابعی که واروخوانه بودنِ لغات رو تشخیص میده با بیانیه‌ی case بنویسیم:

pal xs =
  case xs == reverse xs of
    True -> "yes"
    False -> "no"

این تابع رو با عبارتِ ‏‎where‎‏ هم میشه نوشت؛ برای وقتهایی که ممکنه بخواین باز هم از ‏‎y‎‏ استفاده کنین:

pal' xs =
  case y of
    True -> "yes"
    False -> "no"
  where y = xs == reverse xs

در هر دو صورت، اول تابعِ تساوی نوشته ِ ورودی رو با معکوس ِش چک می‌کنه. اگه ‏‎True‎‏ برگشت، پس نوشته ِ ورودی یه واروخوانه است و تابع میگه ‏‎"yes"‎‏. اگر هم نه، واروخوانه نیست.

یه مثال دیگه، این بار هم انطباق روی داده‌سازهای ‏‎Bool‎‏. یه نسخه با ‏‎if-then-else‎‏ از این مثال رو قبلاً دیدیم، می‌تونین گرامرهاشون رو مقایسه کنین:

-- greetIfCool3.hs
module GreetIfCool3 where

greetIfCool :: String -> IO ()
greetIfCool xs =
  case cool of
    True  -> putStrLn "eyyyyy. What's shakin'?"
    False -> putStrLn "pshhhh."
  where cool = coolness == "downright frosty yo"

تا اینجا هر بیانیه‌ی case ای که دیدیم تطبیق الگوهای ساده و صریح با ‏‎True‎‏ و ‏‎False‎‏ بودن. در یکی بخش‌های بعد، یه راه دیگه برای نوشتن بیانیه‌ی case می‌بینیم.

تمرین‌ها: ورزش case

نوشتن بیانیه‌های case رو با بازنویسیِ چند تابع تمرین می‌کنیم. بعضی از این توابع رو در فصل‌های قبل دیدین (بعضی‌هاشون هم بعداً با گرامرهای دیگه‌ای باز می‌بینین!)، ولی الان نسخه‌ی جدید‌شون رو می‌نویسین. همه‌ی این تمرین‌ها طوری نوشته شدن که انگار تو فایل منبع هستن، ما هم به شما همین رو پیشنهاد می‌کنیم، که بجای نوشتن اونها مستقیماً تو REPL، از فایل بارگذاری‌شون کنین.

اول بیانیه‌های ‏‎if-then-else‎‏ رو با بیانیه‌های case بازنویسی کنین.

۱.

این تابع باید وقتی ‏‎x‎‏ بزرگتر از ‏‎y‎‏ هست، ‏‎x‎‏ برگردونه.

functionC x y = if (x > y) then x else y

۲.

این تابع به مقادیرِ زوج ۲ تا اضافه می‌کنه، و مابقیِ مقادیر رو دست نخورده برمی‌گردونه.

ifEvenAdd2 n = if even n then (n+2) else n

۳.

تابع زیر مقادیرِ ورودی (‏‎x‎‏) رو با صفر مقایسه می‌کنه و بسته به مثبت یا منفی بودن‌شون یه خروجی میده. اگه ورودی صفر بود چطور؟ شاید لازم باشه یه کم با تابعِ ‏‎compare‎‏ بازی کنین تا بتونین این تابع رو تکمیل کنین.

nums x =
  case compare x 0 of
    LT -> -1
    GT -> 1