۲۱ - ۷مونَدِ توابع
توابع یه نمونه ِ Monad
هم دارن. اولِ این فصل یه مثال دیدین، و احتمالاً طرزِ کارش هم حدس زدین. قبل از اینکه بریم سراغِ نمونه و تایپهاش، به طورِ خیلی ساده نشونش میدیم. با هر سرعتی که فکر میکنین مناسبه این بخش رو پیش برین.
اول چندتا تابع زیر رو فرض کنیم:
foo :: (Functor f, Num a) => f a -> f a
foo r = fmap (+1) r
bar :: Foldable f => t -> f a -> (t, Int)
bar r t = (r, length t)
حالا فرض کنیم یه تابع میخوایم که هردو کار رو انجام بده – یعنی هم یکی به مقادیرِ داخلِ ساختار اضافه کنه، و هم طولِ مقدار رو بگه. اینطوری میشه نوشتش:
froot :: Num a => [a] -> ([a], Int)
froot r = (map (+1) r, length r)
یا میشه با ترکیب ِ دو تابعی که داریم بنویسیمش. اینطور که الان bar
تعریف شده، دوتا آرگومان میگیره. میشه یه نسخه ازش تعریف کنیم که فقط یه آرگومان بگیره، اونطوری هر دو بخشِ توپل به یه آرگومان اعمال میشن. کارِ سادهایه (به تغییرِ تایپ سیگنچر هم دقت کنین):
barOne :: Foldable t => t a -> (t a, Int)
barOne r = (r, length r)
آرگومانها رو یکی کم کرد، اما مثلِ تابعِ foo
به مقادیر توی لیست یکی اضافه نکرد. اینطوری میشه اضافه کرد:
barPlus r = (foo r, length r)
ولی میشه جمعوجورتر هم تعریف کرد. اگه (foo r)
رو آرگومانِ اولِ bar
کنیم، این شکلی میشه:
frooty :: Num a => [a] -> ([a], Int)
frooty r = bar (foo r) r
پس در واقع محیطی درست کردیم که دو تابع منتظرِ یک ورودیاند. برای رسیدن به جوابِ نهایی هردوشون به اون آرگومان اعمال میشن.
یه کوچولو تغییرش بدیم تا به Reader
نزدیکتر بشه:
frooty' :: Num a => [a] -> ([a], Int)
frooty' = \r -> bar (foo r) r
حالا انتزاعیش میکنیم تا فقط مختص همین دو تابع نباشه:
fooBind m k = \r -> k (m r) r
تایپ سیگنچر ِ این نسخهی به شدت پلیمورفیک، اینطوری میشه:
fooBind :: (t2 -> t1)
-> (t1 -> t2 -> t)
-> t2
-> t
چقدر تایپِ t
! دلیلش اینه که با چنین انتزاعی، چیزِ زیادی از تایپها نمیشه دونست. چندتا از حرفها رو عوض میکنیم تا واضحتر بشه. از r
برای آرگومانی که هر دو تابع منتظرش هستن (بخشِ مرتبط با Reader
) استفاده میکنیم:
fooBind :: (r -> a)
-> (a -> r -> b)
-> (r -> b)
اگه اون r
ها رو جدا کنیم، شاید متوجهِ شباهتِ fooBind
به یه نسخهی خیلی انتزاعی و ساده از تابعهایی که قبلاً دیدیم بشین (پرانتزهای اضافی رو برای شفافیتِ بیشتر گذاشتیم):
(>>=) :: Monad m =>
m a -> (a -> (m b)) -> m b
(r -> a) -> (a -> (r -> b)) -> (r -> b)
اینطوری میشه که میرسیم به Monad
ِ توابع. مثلِ نمونههای Functor
و Applicative
، اینجا هم ((->) r)
ساختار ِمونه – یا همون m
در (>>=)
. در بخشِ بعد، از تایپها شروع میکنیم.
نمونه ِ Monad
همونطور که اشاره کردیم، آرگومانِ r
بخشی از ساختار (مونَدی) باقی میمونه:
(>>=) :: Monad m
=> m a -> (a -> m b) -> m b
(>>=) ::
(->) r a -> (a -> (->) r b) -> (->) r b
(>>=) ::
(r -> a) -> (a -> r -> b) -> r -> b
return :: Monad m => a -> m a
return :: a -> (->) r a
return :: a -> r -> a
شاید متوجه شده باشین که return
خیلی شبیهِ یکی از تابعهاییه که تو این کتاب زیاد دیدیم.
بایند رو کنارِ Applicative
ببینیم:
(<*>) :: (r -> a -> b)
-> (r -> a)
-> (r -> b)
(>>=) :: (r -> a)
-> (a -> r -> b)
-> (r -> b)
یا با بایند ِ جابجا شده:
(<*>) :: (r -> a -> b)
-> (r -> a)
-> (r -> b)
(=<<) :: (a -> r -> b)
-> (r -> a)
-> (r -> b)
پس این تایپِ r
هرجا میرین دنبالِتونه، مثلِ یه هاپوی تنها.
مثالی از کاربرد تایپِ Reader
اون مثالی که با Person
و Dog
زدیم یادتون هست؟ حالا با Reader Monad
و گرامر ِ do
نوشتیمش:
-- Reader Monad با
getDogRM :: Person -> Dog
getDogRM = do
name <- dogName
addy <- address
return $ Dog name addy
تمرین: Reader Monad
۱.
Reader Monad
رو بنویسین.
-- رو فراموش نکنین instancesigs
instance Monad (Reader r) where
return = pure
(>>=) :: Reader r a
-> (a -> Reader r b)
-> Reader r b
(Reader ra) >>= aRb =
Reader $ \r -> ???
راهنمایی: با نمونه ِ Applicative
مقایسه کنین و واضحترین تغییری که به ذهنتون میرسه رو اعمال کنین تا کار کنه.
۲.
تابع getDogRM
رو با نوعداده ِ Reader
ِتون بازنویسی کنین.