۲۱ - ۶توابع Applicative هم دارن
تا اینجا چندتا مثال از Applicative
ِ توابع و طرز کارشون دیدیم. حالا میریم سراغ جزئیات.
اول نحوهی اختصاصیسازی تایپها رو ببینیم:
-- Applicative f =>
-- f ~ (->) r
pure :: a -> f a
pure :: a -> (r -> a)
(<*>) :: f (a -> b)
-> f a
-> f b
(<*>) :: (r -> a -> b)
-> (r -> a)
-> (r -> b)
همونطور که در نمونه ِ Functor
دیدیم، r
در Reader
بخشی از ساختار ِ f
ِه. تو این تابع دو آرگومان داریم که هردوشون منتظرِ یه آرگومانِ r
هستن. وقتی اون آرگومان میرسه، هردو تابع اعمال میشن تا یه جواب نهاییِ b
برگردونه.
نمایشِ Applicative
ِ تابع
این مثال شبیه بقیهی مثالهاییه که تو کتاب زدیم، ولی تو این مثال هدفمون نشون دادنِ یه کاربردِ بخصوص از Applicative
ِ توابعه که عموماً استفاده میشه. با چندتا newtype
برای تمایزِ String
های مختلف استفاده میکنیم:
newtype HumanName =
HumanName String
deriving (Eq, Show)
newtype DogName =
DogName String
deriving (Eq, Show)
newtype Address =
Address String
deriving (Eq, Show)
این کار رو کردیم تا تایپها گویاتر باشن و یه موقع با هم قاطیشون نکنیم. کار با یه تایپ مثل این:
String -> String -> String
در دو حالتِ زیر سخت میشه:
۱.
هرکدومشون قرار نیست هر String
ای باشن.
۲.
همهشون به یه نحو پردازش نمیشن. مثلاً با آدرسها و اسمها یه جور برخورد نمیشه.
پس تفاوت رو صریح کنین.
دوتا تایپِ رکورد هم درست میکنیم:
data Person =
Person {
humanName :: HumanName
, dogName :: DogName
, address :: Address
} deriving (Eq, Show)
data Dog =
Dog {
dogsName :: DogName
, dogsAddress :: Address
} deriving (Eq, Show)
چندتا داده برای آزمایش. اگه دوست داشتین تغییرشون بدین:
pers :: Person
pers =
Person (HumanName "Big Bird")
(DogName "Barkley")
(Address "Sesame Street")
chris :: Person
chris = Person (HumanName "Chris Allen")
(DogName "Papu")
(Address "Austin")
تابعمون رو با Reader
و بدون اون اینطوری تعریف میکردیم:
-- Reader بدون
getDog :: Person -> Dog
getDog p =
Dog (dogName p) (address p)
-- Reader با
getDogR :: Person -> Dog
getDogR =
Dog <$> dogName <*> address
Reader
رو نمیبینین؟ اگه یه ذره تایپها رو معینتر کنیم چطور؟
(<$->>) :: (a -> b)
-> (r -> a)
-> (r -> b)
(<$->>) = (<$>)
(<*->>) :: (r -> a -> b)
-> (r -> a)
-> (r -> b)
(<*->>) = (<*>)
-- Reader با
getDogR' :: Person -> Dog
getDogR' =
Dog <$->> dogName <*->> address
چیزی که میخوایم نشون بدیم اینه که ریدِر همیشه Reader
نیست، گاهی اوقات Applicative
یا Monad
ِ منسوب به تایپِ توابعِ نیمه اعمالشده هست، که اینجا میشه r ->
.
این الگو برای این استفاده از Applicative
خیلی رایجه، با liftA2
از یه راهِ دیگه میشه تعریفش کرد:
import Control.Applicative (liftA2)
-- یه تعریف دیگه ،Reader با
getDogR' :: Person -> Dog
getDogR' =
liftA2 Dog dogName address
تایپِ liftA2
:
liftA2 :: Applicative f =>
(a -> b -> c)
-> f a -> f b -> f c
باز هم منتظرِ ورودی از جای دیگه هستیم. بجای اینکه یه آرگومان بدیم و همهی توابع رو بهش اعمال کنیم، آرگومان رو حذف میکنیم و بقیه کارها رو میسپریم به تایپها.
تمرین: درک مطلب
۱.
تابعِ liftA2
رو خودتون بنویسین. میشه بهش مثل تجرید ِ تفاوتِ بینِ getDogR
و getDogR'
فکر کرد.
liftA2 :: Applicative f =>
(a -> b -> c)
-> f a -> f b -> f c
liftA2 = undefined
۲.
تابعِ زیر رو تعریف کنین. باز هم سادهتر از اون چیزیه که به نظر میرسه.
asks :: (r -> a) -> Reader r a
asks f = Reader ???
۳.
نمونه ِ Applicative
برای Reader
رو تعریف کنین.
برای نوشتنِ این نمونه ِ Applicative
از یه توسعه به اسمِ InstanceSigs
استفاده میکنیم. با این توسعهی زبانی میشه تایپِ متود ِ تایپکلاسها رو نوشت. در حالتِ عادی نمیشه برای نمونهها تایپ سیگنچر نوشت. کامپایلر از تایپِ توابع خبر داره، پس معمولاً نیازی به تعیینِ تایپها نیست. ما برای وضوحِ بیشتر این کار رو کردیم تا تایپِ Reader
رو صراحتاً در تایپ سیگنچرها نشون بدیم.
-- این پراگما لازمه
{-# LANGUAGE InstanceSigs #-}
instance Applicative (Reader r) where
pure :: a -> Reader r a
pure a = Reader $ ???
(<*>) :: Reader r (a -> b)
-> Reader r a
-> Reader r b
(Reader rab) <*> (Reader ra) =
Reader $ \r -> ???
چندتا راهنمایی:
a)
برای pure
خاطرتون باشه که میخواین یه تابعی بسازین که یه مقدار از تایپِ r
(که هیچی ازش نمیدونین) میگیره، و یه مقدار با تایپِ a
برمیگردونه. با توجه به اینکه هیچ کاری با r
نمیکنین، کلاً یه جواب بیشتر نداره.
b)
تعریفِ تابعِ اپلای رو براتون شروع کردیم، حالا کاری که باید انجام بده رو توصیف میکنیم، شما هم کُدِش رو بنویسین. اگه تایپِ Reader
رو باز کنین، تایپش اینطور میشه:
(<*>) :: (r -> a -> b)
-> (r -> a)
-> (r -> b)
-- مقایسه کنین fmap این رو با تایپ
fmap :: (a -> b)
-> (r -> a)
-> (r -> b)
فرقشون چیه؟ فرق اینجاست که اپلای برخلافِ fmap
، یه آرگومان از تایپِ r
هم میگیره.
ببینیم چه میکنین.