۱۲ - ۲چگونه یاد گرفتم دست از هراس بردارم و عاشق هیچ چیز نشم

یه بار دیگه تعریفِ ‏‎Maybe‎‏ رو ببینیم:

data Maybe a = Nothing | Just a

لازم نیست خودتون تعریف کنین، چون در ‏‎Prelude‎‏ هست. در هسکل هم خیلی رایجه چون برای وقتهایی که هیچ مقدار معقولی برای تایپ مورد نظرمون، ‏‎a‎‏، نداریم، یه مقدارِ پیش‌فرض ِ ‏‎Nothing‎‏ در اختیارمون میذاره.*

*

م. لغت ‏‎Nothing‎‏ معنای "هیچ چیز،" لغت ‏‎Just‎‏ در اینجا معنای "فقط،" و خودِ ‏‎Maybe‎‏ هم معنای "شاید" رو میده. عنوان این بخش هم شوخی با ‏‎Nothing‎‏ و تیترِ یک فیلم سینمایی ِه.

تابعِ زیر یه تابعِ خیلی ساده‌ست. تو این تابع با اعدادِ فرد کارهای زیادی میشه کرد – میشه دست نخورده بَرِشون گردوند، به نحوی متفاوت از اعدادِ زوج تغییرشون بدیم، میشه صفر برگردونیم، یا یه نشان ِ صریح خروجی بدیم که بگه به خاطر زوج نبودن‌ش تابع هم کاری انجام نداد:

ifEvenAdd2 :: Integer -> Integer
ifEvenAdd2 n =
  if even n then n+2 else ???

چطور کاری کنیم بگه: "ببین، این عددی که بهم دادی زوج نبود، من هم چیزی برات ندارم؟" بجای اینکه قولِ یه جوابِ ‏‎Integer‎‏ بدیم، می‌تونیم ‏‎Maybe Integer‎‏ برگردونیم:

ifEvenAdd2 :: Integer -> Maybe Integer
ifEvenAdd2 n =
  if even n then n+2 else Nothing

هنوز کامل نیست، صحیح هم نیست. با اینکه ‏‎Nothing‎‏ تایپِ ‏‎Maybe a‎‏ داره و ‏‎a‎‏ هم تایپی که سازنده ِ ‏‎Maybe‎‏ بهش اعمال میشه رو می‌گیره، تایپِ ‏‎n+2‎‏ هنوز ‏‎Integer‎‏ ِه. باید اون رو در داده‌ساز ِ دیگه‌ی ‏‎Maybe‎‏ بپوشونیم: یعنی ‏‎Just‎‏. اگه همینطور بارگذاریش کنین، این خطا رو می‌گیرین:

<interactive>:9:75:
Couldn't match expected type
  ‘Maybe Integer’
  with actual type ‘Integer’

In the first argument of ‘(+)’, namely ‘n’
  In the expression: n + 2

اینطوری درست میشه:

ifEvenAdd2 :: Integer -> Maybe Integer
ifEvenAdd2 n =
  if even n then Just (n+2) else Nothing

پرانتز لازم بود، چون تابع به نزدیک‌ترین مقدار اعمال میشه (اعمال تابع بیشترین تقدم رو داره). اگه پرانتزها رو نمیذاشتیم، کامپایلر اینطور پارس می‌کرد: ‏‎(Just n) + 2‎‏، که غلط می‌بود و خطای تایپ می‌داد. حالا تابع‌مون درست شد، و هر وقت هم جوابی نداشته باشه، رُک و روراست میگه!

سازنده‌های هوشمند برای نوع‌داده‌ها

یه تایپِ ‏‎Preson‎‏ رو در نظر بگیریم که دو چیز رو نگه میداره، اسم و سن ِ شخص‌ها. با یه تایپِ ضرب ِ ساده معرفی‌ش می‌کنیم (دقت کنین که ‏‎Name‎‏ و ‏‎Age‎‏ تایپِ مستعار اند):

type Name = String
type Age = Integer

data Person = Person Name Age deriving Show

mkPerson :: Name -> Age -> Maybe Person
mkPerson name age =
  | name /= "" && age >= 0 =
      Just $ Person name age
  | otherwise = Nothing

اگه در REPL بارگذاری کنیم:

Prelude> mkPerson "John Browning" 160
Just (Person "John Browning" 160)

خیلی خوب. چی میشه اگه بهش داده ِ نامساعد بدیم؟

Prelude> mkPerson "" 160
Nothing
Prelude> mkPerson "blah" 0
Just (Person "blah" 0) 
Prelude> mkPerson "blah" (-9001)
Nothing

به ‏‎mkPerson‎‏ یه سازنده‌ی هوشمند میگیم. فقط در صورتی داده ای از یه تایپ درست می‌کنه که شرایط خاصی فراهم باشن، در غیر اینصورت با یه نشان ِ صریح* میگه که اون شرایط برقرار نبودن.

*

م. اینجا منظور همون ‏‎Nothing‎‏ ِه.

این خیلی بهتر شده. اما اگه بخوایم بدونیم که اسم غلط بوده، یا سن، یا هردو، اون موقع چطور؟ شاید بخوایم به کاربر بگیم که کجا اشتباه شده. خوشبختانه برای این کار هم یه نوع‌داده داریم!