۱۶ - ۸تغییر دادن آرگومان تایپیِ اعمالنشده
دیدیم که 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
هست. تابعی که از اطرافِ ساختار اعمال میکنیم، فقط میتونه داخلیترین آرگومان رو تغییر بده.