۸ - ۳تهی یا 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 _ = NothingPrelude> :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 _ = NothingPrelude> :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 رو بیشتر توضیح میدیم.