۸ - ۳تهی یا bottom

تهی یا ⊥ در هسکل، به محاسباتی میگیم که در رسیدن به یه مقدار خروجی موفق نمیشن. دو مورد اصلی از تهی، محاسباتی که با خطا مواجه میشن، و محاسباتی که اصلاً به پایان نمی‌رسن هستن. در منطق، ⊥ برای false به کار میره. چند راه برای رسیدن به تهی رو ببینیم:

Prelude> let x = x in x
*** Exception: <<loop>>

اینجا GHCi تشخیص داد که ‏‎let x = x in x‎‏ هیچ وقت به جواب نمیرسه و اتصال-کوتاه‌ش کرد. این یه مثال از تهی ِه و هیچ وقت نتیجه نمیده. نکته اینکه اگه از ویندوز استفاده می‌کنین، GHCi ممکنه گیر کنه و اون استثنا رو نده.

در مثال بعد تابعی تعریف می‌کنیم که تهی برگردونه:

f :: Bool -> Int
f True = error "blah"
f False = 0

در GHCi امتحان‌ش کنیم:

Prelude> f False
0
Prelude> f True
*** Exception: blah

در حالتِ اول، ‏‎f False‎‏ رو حساب کردیم و ‏‎0‎‏ گرفتیم، که تهی نبود. ولی وقتی ‏‎f True‎‏ رو حساب کردیم، یه استثنا گرفتیم (که راهی برای بیان کردنِ شکست در یه محاسبه‌ست). این هم تهی به حساب میاد.

توابع ناقص یه نمونه‌ی دیگه از تهی اند. اگه مثال قبل رو اینطور در نظر بگیریم:

f :: Bool -> Int
f False = 0

تایپ‌ش تغییری نکرده و همون خروجی رو میده. فقط حالتِ ‏‎f True = error "blah"‎‏ رو از تعریفِ تابع حذف کردیم. این راه‌حلی برای مشکلِ مثال قبل نیست، فقط یه استثنا ِ دیگه میده. میشه تو GHCi دید:

Prelude> let f :: Bool -> Int; f False = 0
Prelude> f False
0
Prelude> f True
*** Exception: 6:23-33
      Non-exhaustive patterns in function f

اون مقدارِ ‏‎error‎‏ هنوز هست، ولی به خاطرِ کامل نبودن تابع‌مون (یعنی در نظر نگرفتن همه‌ی حالت‌ها)، خودبه‌خود برای حالت پشتیبان نوشته شد. به همین دلیل که همه‌ی حالت‌های ممکن رو تعریف نکردیم، مثلاً به واسطه‌ی یه حالتِ ‏‎otherwise‎‏، تابع بالا در واقع اینطور تعریف شده:

f :: Bool -> Int
f False = 0
f _ = error $ "*** Exception: "
           ++ "Non-exhaustive "
           ++ "patterns in function f"

تابع ناقص تابعی‌ه که همه‌ی ورودی‌هاش رو به عهده نمی‌گیره. یه تابع کامل همه رو در نظر می‌گیره. چطور تابع ‏‎f‎‏ ِمون رو کامل کنیم؟ یک راهش استفاده از نوع‌داده ِ ‏‎Maybe‎‏ ِه:

data Maybe a = Nothing | Just a

نوع‌داده ِ ‏‎Maybe‎‏ یه آرگومان می‌گیره. در حالت اول، ‏‎Nothing‎‏، آرگومانی وجود نداره؛ که در واقع بدون برگردوندنِ تهی، راهی برای بیانِ بی‌جواب بودن یا داده نداشتنِ تابع‌مون ِه. حالت دوم، ‏‎Just a‎‏ یه آرگومان می‌گیره که امکانِ برگردوندنِ داده ِ مورد نظر رو میده. ‏‎Maybe‎‏ همه‌ی کاربردهای مقدارِ ‏‎nil‎‏، و اکثر کاربردهای تهی رو جبران می‌کنه. اینطور میشه ازش در ‏‎f‎‏ استفاده کرد:

f :: Bool -> Maybe Int
f False = Just 0
f _ = Nothing

دقت کنین که هم تایپ و هم هر دو حالت‌ها تغییر کردن. نه تنها ‏‎error‎‏ رو با ‏‎Nothing‎‏ عوض کردیم، مقدارِ ‏‎0‎‏ هم بُردیم زیرِ داده‌ساز ِ ‏‎Just‎‏ از ‏‎Maybe‎‏. اگه این کار رو نکنیم، با بارگذاری ِ کُد خطای تایپ می‌گیریم:

f :: Bool -> Maybe Int
f False = 0
f _ = Nothing
Prelude> :l code/brokenMaybe1.hs
[1 of 1] Compiling Main

code/brokenMaybe1.hs:3:11:
    No instance for (Num (Maybe Int))
    arising from the literal ‘0’
    In the expression: 0
    In an equation for ‘f’: f False = 0

تایپِ مقدارِ صفر ‏‎Num a => a‎‏ ِه، و دلیلِ این خطای تایپ اینه که نمی‌تونه یه نمونه از ‏‎Num‎‏ برای ‏‎Maybe Int‎‏ پیدا کنه. اگه تایپ‌مون رو واضح‌تر بیان کنیم:

f :: Bool -> Maybe Int
f False = 0 :: Int
f _ = Nothing
Prelude> :l code/brokenMaybe2.hs
[1 of 1] Compiling Main

code/brokenMaybe2.hs:3:11:
    Couldn't match expected type
      ‘Maybe Int’ with actual type ‘Int’
    In the expression: 0 :: Int
    In an equation for ‘f’: f False = 0 :: Int

یه کم جلوتر ‏‎Maybe‎‏ رو بیشتر توضیح میدیم.