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