۲۱ - ۷مونَدِ توابع
توابع یه نمونه ِ 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 ِتون بازنویسی کنین.