۱۶ - ۸تغییر دادن آرگومان تایپیِ اعمال‌نشده

دیدیم که ‏‎f‎‏ باید یک تایپِ گونه‌بالا باشه و نمونه‌های ‏‎Functor‎‏ باید از دو قانون تبعیت کنن. چندتا fmap ِ ساده هم دیدیم. می‌دونیم که هدف از fmap کردن، تغییرِ آرگومان‌های تایپیِ داخلی و دستکاری نکردنِ ساختار ِ بیرونی‌ه.

خیلی وقت پیش، اوایل فصل متوجه شدیم که ‏‎fmap‎‏ روی توپل فقط مقدارِ دوم‌ش (‏‎b‎‏) رو تغییر میده. مشابهِ این رو با ‏‎Either‎‏ هم دیدیم، و گفتیم که بعداً توضیح میدیم. دوباره در مثالِ ‏‎Heisenberg‎‏ چنین چیزی رو دیدیم. حالا وقتش رسیده که ببینیم با این شرایط که فقط می‌تونیم داخلی‌ترین آرگومانِ تایپی رو تغییر بدیم، چه اتفاقی برای آرگومان‌های دیگه (در صورتِ وجود) میوفته.

با چندتا تایپِ کانونیک (م. یا استاندارد) شروع می‌کنیم:

data Two a b =
  Two a b
  deriving (Eq, Show)

data Or a b =
    First a
  | Second b
  deriving (Eq, Show)

شاید متوجه شده باشین که اینها همون ‏‎(,)‎‏ و ‏‎Either‎‏ اند (تایپ‌های جامع برای تایپ‌های ضرب و جمع که باهاشون هر ترکیبی از و و یا رو میشه درست کرد. ولی هردوی اینها از گونه ِ ‏‎* -> * -> *‎‏ هستن که با ‏‎Functor‎‏ سازگاری ندارن، پس چطور باید براشون نمونه ِ ‏‎Functor‎‏ بنویسیم؟

همینطوری نمیشه، چون کایندهاشون جور نیستن:

instance Functor Two where
  fmap = undefined

instance Functor Or where
  fmap = undefined

اعمال ناقص ِ توابع رو بلدیم، چنین چیزی رو هم قبلاً دیدیم:

Prelude> :k Either
Either :: * -> * -> *
Prelude> :k Either Integer
Either Integer :: * -> *
Prelude> :k Either Integer String
Either Integer String :: *

پس با گونه‌ها هم، اعمال ِ نوع‌ساز به آرگومان‌های تایپی، مشابهِ اعمال ِ توابع به مقادیر کار می‌کنه. قبلاً این رو با اعمال ِ نوع‌ساز به تایپ‌های معین نشون داده بودیم؛ ولی امکان‌ش هست که نوع‌ساز رو به یه متغیرِ تایپ که نماینده‌ی یه ثابت تایپ هست هم اعمال کنیم، و همون نتیجه رو بگیریم.

پس برای درست کردنِ ناسازگاری گونه‌ها در تایپ‌های ‏‎Two‎‏ و ‏‎Or‎‏، هر کدوم از نوع‌سازها رو به یکی از آرگومان‌هاشون اعمال می‌کنیم تا گونه‌ِشون ‏‎* -> *‎‏ بشه:

-- استفاده کردیم تا a ما از
-- واضح باشه کدوم تایپ اعمال
-- .شده، ولی خود حرف مهم نیست

instance Functor (Two a) where
  fmap = undefined

instance Functor (Or a) where
  fmap = undefined

الان تایپچکر دیگه گیر نمیده، ولی هنوز ‏‎fmap‎‏ رو براشون تعریف نکردیم، پس ادامه بدیم. اول میریم سراغِ ‏‎Two‎‏:

instance Functor (Two a) where
  fmap f (Two a b) = Two $ (f a) (f b)

این کار نمی‌کنه، چون ‏‎a‎‏ بخشی از ساختار ِ فانکتوری ِه (‏‎f‎‏). قرار بود به هیچ‌جای ‏‎f‎‏ ای که در تایپِ ‏‎fmap‎‏ اشاره شده دست نزنیم، پس نمی‌تونیم تابع (که در تعریفِ ‏‎fmap‎‏ اسم‌ش رو ‏‎f‎‏ گذاشتیم) رو به ‏‎a‎‏ اعمال کنیم؛ ‏‎a‎‏ دیگه قابل دسترس نیست.

fmap :: Functor f => (a -> b) -> f a -> f b

-- :هست، چون (Two a) برابر با f اینجا

class Functor f where
  fmap :: (a -> b) -> f a -> f b

instance Functor (Two a) where

-- یادتون باشه که اسم‌ها فراتر از
-- .رابطه‌شون با همدیگه، هیچ معنایی ندارن
  :: (a -> b) -> (Two z) a -> (Two z) b

در نتیجه برای درست کردنِ نمونه ِ ‏‎Functor‎‏، باید کاری با مقدارِ سمت چپ در ‏‎Two‎‏ نداشته باشیم (جزئی از ساختار ِ ‏‎f‎‏ ِه)، و تابع‌مون رو فقط به بیرونی‌ترین مقدار (که اینجا اسم‌ش رو گذاشتیم ‏‎b‎‏) اعمال کنیم:

instance Functor (Two a) where
  fmap f (Two a b) = Two a (f b)

با اینکه در ‏‎Or‎‏ با دو مقدارِ ممکن و مستقل از هم سروکار داریم، باز هم همون محدودیت‌ها رو باید رعایت کنیم:

instance Functor (Or a) where
  fmap _ (First a) = First a
  fmap f (Second b) = Second (f b)

آرگومانِ اول اعمال شده، پس حالا دیگه بخشی از ‏‎f‎‏ هست. تابعی که از اطرافِ ساختار اعمال می‌کنیم، فقط می‌تونه داخلی‌ترین آرگومان رو تغییر بده.