۴ - ۶من رو بول بزن

تایپ Bool از Prelude میاد، و همونطور که دیدیم، یک تایپِ جمع با دو داده‌ساز ِه:

data Bool = False | True

این تعریف، یک نوع‌داده با نوع‌ساز ِBool درست می‌کنه. نوع‌سازها در تایپ سیگنچرها نوشته میشن، و نه تویِ بیانیه‌های سطحِ جمله‌ای.نوع‌ساز ِBool هیچ آرگومانی نمی‌گیره (بعضی نوع‌سازها می‌گیرن). تعریفِ بالا دو تا داده‌ساز هم ایجاد می‌کنه، True و False. هر تابعی که مقادیرِ تایپ Bool قبول می‌کنه، حتماً باید هر دو حالتِ True یا False رو در نظر بگیره؛ در تایپِ تابع نمیشه تعیین کرد که فقط یک مقدار رو قبول کنه. اگه بخوایم تعریفِ بالا رو به کلام بگیم، اینطور میشه: تایپ Bool با دو مقدارِ True یا False ارائه میشه.

یادتون باشه که تایپ هر مقداری رو میشه از GHCi پرسید، مثلِ توابع:

Prelude> :t True
True :: Bool
Prelude> :t "Julie"
"Julie" :: [Char]

یه کم با Bool سرگرم شیم. اول not رو دوره کنیم:

Prelude> :t not
not :: Bool -> Bool
Prelude> not True
False

True و False رو با حرفِ اولِ بزرگ می‌نویسیم، چون داده‌ساز اند. اگه not رو به اونها بدونِ حرفِ بزرگ اعمال کنین چی میشه؟

یه چیز دیگه رو امتحان کنیم، یه ذره پیچیده‌تر:

Prelude> let x = 5 
Prelude> not (x == 5)
False
Prelude> not (x > 7)
True

می‌دونیم که توابعِ قیاس به مقادیر Bool ساده میشن، پس می‌تونن آرگومانِ not بشن.

حالا عملگرهای میانوند ِمنطق بولی رو نگاه کنیم، و ببینیم چطور می‌تونیم از Bool و این توابع استفاده کنیم.

اول از همه، (&&) اوپراتورِ میانوند ِعطف ِبولی‌ه. مثال اول رو اینطور می‌خونیم، true و true.

Prelude> True && True
True 
Prelude> (8 > 4) && (4 > 5)
False
Prelude> not (True && True)
False

عملگر میانوند برای فصل ِبولی، (||) ِه. پس خطِ اول false یا true خونده میشه:

Prelude> False || True
True
Prelude> (8 > 4) || (4 > 5)
True
Prelude> not ((8 > 4) || (4 > 5))
False

با دستور i: در GHCi، می‌تونیم اطلاعاتِ نوع‌داده‌هایی که در گستره هستن رو نگاه کنیم (اگه در گستره نباشن، باید ماژول‌شون رو وارد کنیم). گستره یعنی جاهایی که یک اسمِ تعریف شده برای یه بیانیه اعتبار داره. وقتی یه چیزی در گستره هست، یعنی میشه از اون بیانیه به واسطه‌ی اسمش استفاده کرد؛ حالا یا به خاطرِ اینکه توی تابع یا ماژولِ‌مون تعریف شده، یا به خاطر اینکه وارد شده. پس در برنامه‌مون مرئیِ‌ه. همه‌ی چیزهایی که در Prelude ِهسکل درست شدن، خودبه‌خود وارد میشن و در گستره قرار می‌گیرن. فعلاً فقط همین‌ها رو لازم داریم و لازم نیست همه‌ی تابع‌هامون رو از اول بنویسیم.

تمرین‌ها: مشکلات رو پیدا کن

بعضی از کدهای زیر ایراد دارن – اصلاً کامپایل نمیشن! میدونین چی کار کنین.

۱.

not True && true

۲.

not (x = 6)

۳.

(1 * 2) > 5

۴.

[Merry] > [Happy]

۵.

[1, 2, 3] ++ "look at me!"

شروط با if-then-else

هسکل، دستورِ if نداره (if statement)، ولی بیانیه‌ی if داره (if expression). یه گرامر داخلی‌ه، که با تایپِ Bool کار می‌کنه.

Prelude> let t = "Truthin'"
Prelude> let f = "Falsin'"
Prelude> if True then t else f
"Truthin'"

بیانیه‌ی if True به True محاسبه میشه، پس t رو برمی‌گردونیم.

Prelude> if False then t else f
"Falsin'"
Prelude> :t if True then t else f
if True then "Truthin'" else "Falsin'"
  :: [Char]

و if False به False ساده میشه، پس مقدار else رو برمی‌گردونیم. تایپِ کلِ بیانیه String (یا [Char]) ِه، همون تایپی که تایپِ مقدارِ نهاییِ بیانیه‌ست.

ساختار اینطوریه:

If CONDITION
then EXPRESSION_A
else EXPRESSION_B

اگه شرط یا CONDITION (که حتماً باید به یه Bool ساده بشه) مقدارِ True داشته باشه، EXPRESSION_A نتیجه میشه، در غیر اینصورت، EXPRESSION_B.

میشه به بیانیه‌های if به چشمِ یه راهی برای انتخابِ بین دو مقدار نگاه کرد. تنها محدودیتی که برای بیانیه‌های جلوی if وجود داره، اینه که باید به یه مقدارِ Bool ساده بشن. تایپِ بیانیه‌های جلوی then و else هم باید منطبق باشن، مثل زیر:

Prelude> let x = 0
Prelude> let a = "AWESOME"
Prelude> let w = "wut"
Prelude> if (x + 1 == 1) then a else w
"AWESOME"

مراحل ساده شدنش از این قراره:

-- داریم:
x = 0

if (x + 1 == 1) then "AWESOME" else "wut"
-- صفر است x

if (0 + 1 == 1) then "AWESOME" else "wut"
-- اون جمع رو ساده می‌کتیم تا ببینیم
-- آیا برابر با 1 هست یا نه

if (1 == 1) then "AWESOME" else "wut"
-- آیا 1 برابر 1 هست؟

if True then "AWESOME" else "wut"
-- انتخاب کن Bool یکی رو براساس مقدار

"AWESOME"
-- !حله

ولی این کار نمی‌کنه:

Prelude> let dog = "adopt a dog"
Prelude> let cat = "or a cat"
Prelude> let x = 0
Prelude> if x * 100 then dog else cat

<interactive>:15:7:
    No instance for (Num Bool) arising
    from a use of ‘*’

    In the expression: (x * 100)
    In the expression:
        if (x * 100)
          then "adopt a dog"
          else "or a cat"
    In an equation for ‘it’:
        it = if (x * 100)
               then "adopt a dog"
               else "or a cat"

دایل این خطا، تایپِ شرطیِ‌ه که به بیانیه‌ی if دادیم، ‏‎Num a => a‎‏، که Bool نیست و تایپکلاسِ Num هم برای Bool تعریف نشده. ساده‌تر بگیم، (x * 100) به یه عدد ساده میشه و اعداد مقادیر واقعی نیستن. اگه یه چیزی مثل x * 100 == 0 یا x * 100 == 9001 بود، اون موقع کار می‌کرد، چون بررسیِ یه تساوی بود و به یه Bool ساده میشد.

مثال زیر یه تابع‌ه که از یه مقدار Bool در بیانیه‌ی if استفاده می‌کنه:

-- greetIfCool1.hs
module GreetIfCool1 where

greetIfCool :: String -> IO ()
greetIfCool coolness =
  if cool
    then putStrLn "eyyyyy. What's shakin'?"
  else
    putStrLn "pshhhh."
  where cool = coolness == "downright frosty yo"

اگه تو REPL چک کنین، چنین چیزی میشه:

Prelude> :l greetIfCool1.hs
[1 of 1] Compiling GreetIfCool1
Ok, modules loaded: GreetIfCool1.
Prelude> greetIfCool "downright frosty yo"
eyyyyy. What's shakin'?
Prelude> greetIfCool "please love me"
pshhhh.

اگه می‌خواستین، میشد cool ِداخل greetIfCool رو، بجای یه مقدار که مستقیماً با آرگومانِ تابع تعریف شده، به عنوان یه تابع بنویسین. اینطوری:

-- greetIfCool2.hs
module GreetIfCool2 where

greetIfCool :: String -> IO ()
greetIfCool coolness =
  if cool coolness
    then putStrLn "eyyyyy. What's shakin'?"
  else
    putStrLn "pshhhh."
  where cool v = v == "downright frosty yo"