۲۱ - ۶توابع 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 هم میگیره.
ببینیم چه میکنین.