۱۷ - ۴فانکتورهای اپلیکتیو، فانکتورهای مانویدی‌اند

اول به یه چیزی دقت کنیم:

 ($)  ::   (a -> b) ->   a ->   b
(<$>) ::   (a -> b) -> f a -> f b
(<*>) :: f (a -> b) -> f a -> f b

از قبل می‌دونیم که ‏‎$‎‏ در واقع یه تابعِ میانوند ِ هیچ‌کاره‌ست که فقط نقشِ افزایشِ تقدم ِ سمتِ راست و کاهشِ پرانتزها رو داره. ولی اینجا کمک می‌کنه مفهومی رو نشون بدیم.

وقتی با ‏‎<$>‎‏ (مستعار ِ ‏‎fmap‎‏) مقایسه‌ش می‌کنیم، اولین تغییری که متوجه میشیم اینه که ‏‎(a -> b)‎‏ رو از روی ‏‎f‎‏ که مقدارِ ‏‎a‎‏ رو پوشونده لیفت، و بعد بهش اعمال می‌کنیم.

به اَپ یا ‏‎<*>‎‏ (متود ِ اعمال ِ ‏‎Applicative‎‏) که می‌رسیم، می‌بینیم که تابع‌مون هم داخلِ اون ساختار ِ فانکتوری پوشونده شده. حالا می‌رسیم به مانویدی در "فانکتور مانویدی":

:: f (a -> b) -> f a -> f b

-- دو آرگومان تابع اینها هستن:

f (a -> b)
-- و
f a

اگه فرض کنیم با نادیده گرفتن اون ساختار ِ فانکتوری، ‏‎(a -> b)‎‏ رو به ‏‎a‎‏ اعمال کنیم تا ‏‎b‎‏ بگیریم، باز هم یه مشکل هست، چون خروجی‌مون باید ‏‎f b‎‏ باشه. وقتی با ‏‎fmap‎‏ کار می‌کردیم، فقط با یک ساختار سروکار داشتیم، پس دست‌نخورده رهاش می‌کردیم. ولی حالا دوتا ساختار با تایپِ ‏‎f‎‏ داریم که برای رسیدن به ‏‎f b‎‏ باید یه کاری باهاشون بکنیم. نمیشه کاری به کارشون نداشته باشیم؛ باید یه جوری با هم متحدشون کنیم. می‌دونیم که ‏‎f‎‏ در طولِ تایپ تغییری نمی‌کنه، پس قطعاً ساختارِ هردوشون از یه تایپ‌ه. اگه بخش‌های ساختاری رو از بخش‌های تابعی جدا کنیم، شاید ببینیم چه چیزی لازم داریم:

:: f (a -> b) -> f a -> f b

   f             f      f
   (a -> b)      a      b

قبلاً یه چیزی نداشتیم که دوتا مقدار از یه تایپ می‌گرفت و یه مقدار از همون تایپ برمی‌گردوند؟ با فرضِ اینکه ‏‎f‎‏ تایپی با یه نمونه ِ ‏‎Monoid‎‏ باشه، راهِ خوبی برای ترکیب‌شون داریم:

mappend :: Monoid a => a -> a -> a

پس با ‏‎Applicative‎‏، یه مانوید برای ساختار‌ِمون داریم و یه اعمالِ تابع برای مقادیرمون!

mappend ::    f             f      f
$       ::   (a -> b)       a      b

(<*>)   :: f (a -> b)    -> f a -> f b

-- Functor از fmap به علاوه‌ی
-- .نگاشت کرد f تا بشه از روی

پس میشه گفت یه ‏‎Monoid‎‏ رو به یه ‏‎Functor‎‏ میخ کردیم تا از تابع‌هایی که داخل ساختار‌های اضافه هستن استفاده کنیم. به عبارتِ دیگه، داریم همون ساختاری که قبلاً با ‏‎Functor‎‏ از روش نگاشت می‌کردیم رو به اعمالِ تابع اضافه می‌کنیم. با چندتا مثالِ آشنا بیشتر توضیح میدیم:

-- List
[(*2), (*3)] <*> [4, 5]

=

[2 * 4, 2 * 5, 3 * 4, 3 * 5]

-- ساده‌شده
[8,10,12,15]

پس در ‏‎f (a -> b) -> f a -> f b‎‏ چه چیزی به ‏‎(a -> b)‎‏ اضافه شده بود؟ در این مورد، "لیست بودن." با اینکه اعمال ِ هرکدوم از ‏‎(a -> b)‎‏ ها به مقادیرِ تایپِ ‏‎a‎‏ خیلی معمولی‌ه، اینجا برخلافِ ‏‎Functor‎‏ ِ لیست که یک تابع می‌داشتیم، یه لیست از توابع داریم.

اما لیست‌ها تنها ساختار‌هایی که میشه به مقادیرمون اضافه کنیم نیستن – اصلاً اینطور نیست! میشه ‏‎Maybe‎‏ هم باشه:

Just (*2) <*> Just 2
=
Just 4

Just (*2) <*> Nothing
=
Nothing

Nothing <*> Just 2
=
Nothing

Nothing <*> Nothing
=
Nothing

در ‏‎Maybe‎‏، فانکتور تابع رو با احتمالِ وجود نداشتنِ یه مقدار اعمال می‌کنه. با ‏‎Applicative‎‏، اون تابع هم ممکنه وجود نداشته باشه. چندتا مثالِ خوشگل و طولانی از چنین حالتی می‌بینیم – که چطور ممکنه به نقطه‌ای برسیم که هیچ تابعی برای اعمال نداشته باشیم – یه ذره جلوتر، و نه فقط با ‏‎Maybe‎‏، با ‏‎Either‎‏ و یه تایپِ جدید به اسمِ ‏‎Validation‎‏ هم می‌بینیم.

مانوید رو نشونم بده

اگه خاطرتون باشه، نمونه ِ ‏‎Functor‎‏ برای توپل ِ دوتایی، مقدارِ اول در توپل رو نادیده می‌گیره:

Prelude> fmap (+1) ("blah", 0)
("blah",1)

اما ‏‎Applicative‎‏ ِ توپل ِ دوتایی، نقشِ مانوید در ‏‎Applicative‎‏ رو به خوبی نشون میده. با دستور ‏‎:info‎‏ برای ‏‎(,)‎‏ در REPL، میشه این رو دید:

Prelude> :info (,)
data (,) a b = (,) a b
-- Defined in ‘GHC.Tuple’
 ...
 instance Monoid a
       => Applicative ((,) a)
-- Defined in ‘GHC.Base’
 ...
 instance (Monoid a, Monoid b)
        => Monoid (a, b)

در نمونه ِ ‏‎Applicative‎‏ برای توپل ِ دوتایی، نیازی به یه ‏‎Monoid‎‏ برای ‏‎b‎‏ نداریم چون اون رو از طریقِ اعمال تابع درست می‌کنیم. ولی برای مقدارِ اولِ ‏‎Monoid‎‏ لازم داریم، چون دوتا ازش داریم و باید به نحوی اون دوتا رو یکی کنیم:

Prelude> ("Woo", (+1)) <*> (" Hoo!", 0)
("Woo Hoo!",1)

دقت کنین که برای مقدارِ ‏‎a‎‏ هیچ تابعی اعمال نکردیم، ولی انگار با جادو خودشون با هم ترکیب شدن؛ این به خاطرِ نمونه ِ ‏‎Monoid‎‏ برای مقادیرِ ‏‎a‎‏ ِه. تابعی که در جایگاهِ ‏‎b‎‏ ِ توپل ِ سمتِ چپ قرار گرفته، به مقداری که در جایگاهِ ‏‎b‎‏ ِ توپل ِ سمتِ راست قرار گرفته اعمال شده تا یه جواب درست کنه. همین اعمالِ تابع، دلیلِ بی‌نیاز بودنِ ‏‎b‎‏ به یه نمونه ِ ‏‎Monoid‎‏ ِه.

چندتا مثالِ دیگه هم ببینیم. به نحوه‌ی ترکیب ِ مقادیرِ ‏‎a‎‏ خوب توجه کنین:

Prelude> import Data.Monoid
Prelude> (Sum 2, (+1)) <*> (Sum 0, 0)
(Sum {getSum = 2},1)

Prelude> (Product 3, (+9)) <*> (Product 2, 8)
(Product {getProduct 6 = False}, 17)

Prelude> (All True, (+1)) <*> (All False, 0)
(All {getAll = False},1)

چه ‏‎Monoid‎‏ ای باشه مهم نیست، فقط باید بشه با هم ترکیب بشن، یا بین‌شون یکی انتخاب بشه.

مانوید و اپلیکتیو ِ توپل کنار هم

اگه نمی‌بینین، پلک‌هاتون رو به هم نزدیک کنین.

instance (Monoid a, Monoid b)
      => Monoid (a,b) where
    mempty = (mempty, mempty)
    (a, b) `mappend` (a',b') =
      (a `mappend` a', b `mappend` b')

instance Monoid a
      => Applicative ((,) a) where
    pure x = (mempty, x)
    (u, f) <*> (v, x) =
      (u `mappend` v, f x)

مانوید و اپلیکتیو ِ ‏‎Maybe‎‏

با اینکه اپلیکتیو ها، فانکتور های مانویدی هستن، حواستون باشه که برمبنای این، چیزی رو فرض نکنین. یه دلیل‌ش اینه که نه تضمینی هست، و نه لازمه که نمونه‌های ‏‎Monoid‎‏ و ‏‎Applicative‎‏ از مانوید ِ یکسان برای ساختار‌ِشون استفاده کنن، بخشِ فانکتوری هم ممکنه رفتارش رو تغییر بده. با همه‌ی اینها میشه ‏‎Monoid‎‏ رو در نحوه‌ی تطبیق الگو ِ ‏‎Applicative‎‏ روی ‏‎Just‎‏ و ‏‎Nothing‎‏ دید و با این ‏‎Monoid‎‏ مقایسه کرد:

instance Monoid a => Monoid (Maybe a) where
  mempty = Nothing
  mappend m Nothing = m
  mappend Nothing m = m
  mappend (Just a) (Just a') =
    Just (mappend a a')

instance Applicative Maybe where
    pure = Just

    Nothing <*> _     = Nothing
    _ <*> Nothing     = Nothing
    Just f <*> Just a = Just (f a)

بعداً چندتا مثال می‌بینیم که چطور نمونه‌های ‏‎Monoid‎‏ ِ مختلف ممکنه نتیجه‌های مختلفی از اپلیکتیو بدن. فعلاً متوجه باشین که تیکه‌ی مانویدی ِ اپلیکتیو ممکنه اون چیزی که شما به عنوانِ ‏‎mappend‎‏ ِ استاندارد یا کانونیک فرض می‌کنین نباشه؛ چون بعضی تایپ‌ها ممکنه چندتا مانوید داشته باشن.