۱۶ - ۱۱نادیدهگرفتن حالتها
فانکتور ِ Maybe و Either رو یه بار دیدیم. حالا با دقتِ بیشتری کاربردشون رو بررسی میکنیم. همونطور که از عنوانِ این بخش پیداست، نمونه ِ Functor ِ این نوعدادهها برای وقتی خوبه که میخوایم حالتِ سمتِ چپ رو نادیده بگیریم (Nothing برای Maybe، و Left برای Either، که معمولاً هم حالتهای خطا یا شکستخورده هستن). fmap اون حالتها رو دست نمیزنه، پس تابعش رو مستقیماً روی مقادیری که لازم داریم اعمال میکنه و حالتهای شکست رو نادیده میگیره.
Maybe
با چندتا تطبیق الگو ِ ساده روی Maybe شروع میکنیم:
incIfJust :: Num a => Maybe a -> Maybe a
incIfJust (Just n) = Just $ n + 1
incIfJust Nothing = Nothing
showIfJust :: Show a => Maybe a -> Maybe String
showIfJust (Just s) = Just $ show s
showIfJust Nothing = Nothingحوصلهسربَره... تازه زیادهنویسی هم کردیم. یکی از زیادهنویسیها حالتِ مشترکشون برای Nothing ِه:
someFunc Nothing = Nothingبعدش هم هردوشون در صورتِ Just بودن، یه تابعی رو به مقدارِ زیرش اعمال میکنن:
someFunc (Just x) = Just $ someOtherFunc xاگه از fmap استفاده کنیم چی میشه؟
incMaybe :: Num a => Maybe a -> Maybe a
incMaybe m = fmap (+1) m
showMaybe :: Show a => Maybe a -> Maybe String
showMaybe s = fmap show sخوب انگار یه کم تَروتمیز شد. هنوز کار میکنه؟
Prelude> incMaybe (Just 1)
Just 2
Prelude> incMaybe Nothing
Nothing
Prelude> showMaybe (Just 9001)
Just "9001"
Prelude> showMaybe Nothing
Nothingآره، fmap دلیلی نداره خودش رو درگیرِ Nothing کنه – مقداری نداره که بخواد باهاش کار کنه، پس انگار همه چیز درست کار میکنه.
با این حال میشه این رو انتزاعیتر کرد. یه راهش اینه که اونها رو کاهشِ اِتا بدیم. یعنی بدون نامگذاریِ آرگومانها بازنویسیشون کنیم:
incMaybe'' :: Num a => Maybe a -> Maybe a
incMaybe'' = fmap (+1)
showMaybe'' :: Show a => Maybe a -> Maybe String
showMaybe'' = fmap showو اینکه لازم نیست حتماً مختصِ Maybe باشن! fmap با همهی نوعدادههایی که نمونه ِ Functor دارن کار میکنه! با استعلام ِ تایپ در GHCi تایپِ جامعترشون رو میشه دید:
Prelude> :t fmap (+1)
fmap (+1)
:: (Functor f, Num b) => f b -> f b
Prelude> :t fmap show
fmap show
:: (Functor f, Show a) => f a -> f Stringحالا میشه خیلی جامعتر بازنویسیشون کنیم:
-- به این خاطر اسمشون رو
-- چون از روی ``lifted'' گذاشتیم
-- لیفت شدن f یه ساختار
liftedInc :: (Functor f, Num b)
=> f b -> f b
liftedInc = fmap (+1)
liftedShow :: (Functor f, Show a)
=> f a -> f String
liftedShow = fmap showهمون رفتار هم دارن:
Prelude> liftedInc (Just 1)
Just 2
Prelude> liftedInc Nothing
Nothing
Prelude> liftedShow (Just 1)
Just "1"
Prelude> liftedShow Nothing
Nothingبا افزایش پلیمورفیسم در تایپِ اون ساختار ِ فانکتوری، در مواردِ بیشتری میشه ازشون استفاده کرد:
Prelude> liftedInc [1..5]
[2,3,4,5,6]
Prelude> liftedShow [1..5]
["1","2","3","4","5"]تمرین: ممکنه
برای نوعدادهای که دقیقاً عینِ Maybe هست، یه نمونه ِ Functor بنویسین. از این نوعداده استفاده میکنیم چون Maybe یه نمونه ِ Functor داره و نمیشه دوباره براش نوشت.
data Possibly a =
LolNope
| Yeppers a
deriving (Eq, Show)
instance Functor Possibly where
fmap = undefinedدر واقع دارین همین تابع رو مینویسین:
applyIfJust :: (a -> b)
-> Maybe a
-> Myabe bEither
تایپِ Maybe بعضی مسائلِ هسکلنویسها رو حل میکنه، اما همهشون رو حل نمیکنه. در یکی از فصلهای قبل دیدیم که گاهی اوقات میخوایم دلیلِ شکستِ یه محاسبه رو بدونیم، تا اینکه فقط بدونیم که شکست خورد. برای چنین چیزی از Either استفاده میکنیم.
دیگه تا الان میدونین که یه نمونه ِ Functor برای Either در base هست تا برنامهنویسهای قدرشناس ازش استفاده کنن. تو چندتا مثال ازش استفاده میکنیم. برای سادگی از همون الگویی که Maybe رو نشون دادیم استفاده میکنیم:
incIfRight :: Num a
=> Either e a
-> Either e a
incIfRight (Right n) = Right $ n + 1
incIfRight (Left e) = Left e
showIfRight :: Show a
=> Either e a
-> Either e String
showIfRight (Right s) = Right $ show s
showIfRight (Left e) = Left eمثلِ قبل میشه با fmap سادهتر بنویسیم تا لازم نباشه حالتِ خطا رو مستقلاً بررسی کنیم:
incEither :: Num a
=> Either e a
-> Either e a
incEither m = fmap (+1) m
showEither :: Show a
=> Either e a
-> Either e String
showEither s = fmap show sباز هم میشه کاهشِ اتا داد تا آرگومانهای واضح حذف بشن:
incEither' :: Num a
=> Either e a
-> Either e a
incEither' = fmap (+1)
showEither' :: Show a
=> Either e a
-> Either e String
showEither' = fmap showدوباره تابعهایی داریم که نباید لزوماً مختص Either باشن:
-- f ~ Either e
liftedInc :: (Functor f, Num b)
=> f b -> f b
liftedInc = fmap (+1)
liftedShow :: (Functor f, Show a)
=> f a -> f String
liftedShow = fmap showیه کم باهاش بازی کنین تا کامل بفهمین چی به چیه.
تمرین کوتاه
۱.
برای نوعدادهای که دقیقاً عینِ Either هست، یه نمونه ِ Functor بنویسین. از این نوعداده استفاده میکنیم چون Either یه نمونه ِ Functor داره و نمیشه دوباره براش نوشت.
data Sum a b =
First a
| Second b
deriving (Eq, Show)
instance Functor (Sum a) where
fmap = undefinedبرای راهنمایی: دارین این تابع رو مینویسین.
applyIfSecond :: (a -> b)
-> (Sum e) a
-> (Sum e) b۲.
چرا نمیشه یه نمونه ِ Functor باشه که تابع رو فقط به First، یا Left در Either اعمال میکنه؟ یه کم قبلتر توضیح دادیم.