۹ - ۳تطبیق الگو روی لیست‌ها

دیدیم که میشه روی داده‌سازها تطبیق الگو انجام داد، و داده‌سازهای لیست هم مستثنی نیستن. در زیر روی اولین آرگومانِ داده‌ساز ِ میانوند ِ ‏‎(:)‎‏ تطبیقِ الگو می‌کنیم و مابقی لیست رو نادیده می‌گیریم:

Prelude> let myHead (x : _) = x
Prelude> :t myHead
myHead :: [t] -> t
Prelude> myHead [1, 2, 3]
1

برعکس‌ش هم میشه انجام داد:

Prelude> let myTail (_ : xs) = xs
Prelude> :t myTail
myTail :: [t] -> [t]
Prelude> myTail [1, 2, 3]
[2, 3]

برای استفاده از این توابع باید حواس‌مون رو جمع کنیم. نه ‏‎myHead‎‏ و نه ‏‎myTail‎‏ حالتی که ورودی یه لیستِ خالی باشه رو در نظر نگرفتن. اگه با لیست خالی امتحان‌شون کنیم، نمی‌تونن تطبیقِ الگو کنن:

Prelude> myHead []
*** Exception:
  Non-exhaustive patterns
    in function myHead

Prelude> myTail []
*** Exception:
  Non-exhaustive patterns
    in function myTail

مشکل اینه که تایپِ ‏‎[a] -> a‎‏ برای ‏‎myHead‎‏ در واقع فریبنده‌ست، چون تایپِ ‏‎[a]‎‏ تضمینی نداره که حتماً یه مقدارِ ‏‎a‎‏ داشته باشه. داشتنِ حداقل یک مقدار رو هم تضمین نمی‌کنه، پس ‏‎myTail‎‏ ممکنه شکست بخوره. یک راه، اضافه کردنِ فرمان پایه هست:

myTail          :: [a] -> [a]
myTail []       = []
myTail (_ : xs) = xs

با این تعریف چنین جواب‌هایی میده:

Prelude> myTail [1..5]
[2,3,4,5]
Prelude> myTail []
[]

استفاده از ‏‎Maybe‎‏

راه بهتر برای چنین مواردی، استفاده از نوع‌داده ای به اسمِ ‏‎Maybe‎‏ ِه. توضیح کاملِ ‏‎Maybe‎‏ رو برای یکی دیگه از فصل‌ها حفظ می‌کنیم، ولی اینجا یه درک کلی از کاربردش پیدا می‌کنین. ایده اینه که حالت شکست رو صراحتاً بیان کنیم؛ با طولانی و پیچیده‌تر شدنِ برنامه‌ها این روش خیلی کاربردی میشه.

با استفاده از ‏‎Maybe‎‏ برای ‏‎myTail‎‏ مثال می‌زنیم. بجای یه فرمانِ پایه که لیست خالی برگردونه، تابعی که با ‏‎Maybe‎‏ نوشته شده ‏‎Nothing‎‏ برمی‌گردونه. همونطور که می‌بینیم، نوع‌داده ِ ‏‎Maybe‎‏ می‌تونه یکی از دو مقدارِ ‏‎Nothing‎‏ یا ‏‎Just a‎‏ رو داشته باشه:

Prelude> :info Maybe
data Maybe a = Nothing | Just a

بازنویسیِ ‏‎myTail‎‏ با ‏‎Maybe‎‏ ساده‌ست:

safeTail        :: [a] -> Maybe [a]
safeTail []     = Nothing
safeTail (x:[]) = Nothing
safeTail (_:xs) = Just xs

دقت کنین که تابع هنوز روی لیست تطبیق الگو انجام میده. فرمان پایه ِ دوم ‏‎safeTail (x:[]) = Nothing‎‏ رو اینطور نوشتیم که اگه ورودی یه لیست تک عضوی بود، خالی بودن دُم ِش رو نشون بده. اگه این فرمان رو نمی‌نوشتیم، جواب برای لیستی که فقط یه مقدارِ سَر داره، ‏‎Just []‎‏ میشد. چند دقیقه وقت بذارین و باهاش بازی کنین تا طرز کارش رو ببینین. بعد هم سعی کنین تابعِ ‏‎myHead‎‏ رو با استفاده از ‏‎Maybe‎‏ بازنویسی کنین.

جلوتر در کتاب نوع‌داده ِ ‏‎NonEmpty‎‏ هم معرفی می‌کنیم که همیشه حداقل یک مقدار داره و مشکلِ لیست خالی رو نداره.