۲۱ - ۷مونَدِ توابع

توابع یه نمونه ِ ‏‎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‎‏ ِتون بازنویسی کنین.