۱۵ - ۱۵تمرینهای فصل
تمرینهای نیمگروه
برای نوعدادههای زیر نمونه ِ Semigroup
تعریف کنین. هرجا لازم بود، برای متغیرهای تایپ محدودیت ِ Semigroup
بذارین. تایپکلاسِ Semigroup
رو از کتابخونه ِ semigroups
بیارین (یا از base
، اگه از ۸ GHC استفاده میکنین)، یا میتونین خودتون تعریفش کنین. هرجا از (<>)
استفاده کردیم، منظورمون mappend
ِ میانوند از تایپکلاسِ Semigroup
ِه.
نکته
برای تایپهایی که تو تمرینها بهتون میدیم، همیشه همهی نمونههایی که ازشون لازم دارین رو مشتق نمیگیریم. دیگه تا این نقطه از کتاب ازتون انتظار داریم خودتون تشخیص بدین کدومها رو لازم دارین و ترتیبش رو بدین.
۱.
همهی نمونههاتون رو با QuickCheck
تست کنین. به خاطر اینکه تنها قانون ِ Semigroup
شرکتپذیری ِه، تنها مشخصهای هم که باید استفاده کنین همونه. حواستون باشه که به احتمال زیاد باید ماژولهای Monoid
و Semigroup
رو وارد کنین، پس مراقبِ تلاقی بینِ دوتا (<>)
ها باشین (البته بسته به نسخهی GHC که دارین).
data Trivial = Trivial deriving (Eq, Show)
instance Semigroup Trivial where
_ <> _ = undefined
instance Arbitrary Trivial where
arbitrary = return Trivial
semigroupAssoc :: (Eq m, Semigroup m)
=> m -> m -> m -> Bool
semigroupAssoc a b c =
(a <> (b <> c)) == ((a <> b) <> c)
type TrivAssoc =
Trivial -> Trivial -> Trivial -> Bool
main :: IO ()
main = quickCheck (semigroupAssoc :: TrivAssoc)
۲.
newtype Identity a = Identity a
۳.
data Two a b = Two a b
-- .دیگه هم بخواین Semigroup راهنمایی: یه
۴.
data Three a b c = Three a b c
۵.
data Four a b c d = Four a b c d
۶.
newtype BoolConj =
BoolConj Bool
-- ^------^ یا عطفِ بولی boolean conjunction .م
کاری که باید انجام بده:
Prelude> (BoolConj True) <> (BoolConj True)
BoolConj True
Prelude> (BoolConj True) <> (BoolConj False)
BoolConj False
۷.
newtype BoolDisj =
BoolDisj Bool
-- ^------^ یا فصلِ بولی boolean disjunction .م
کاری که باید انجام بده:
Prelude> (BoolDisj True) <> (BoolDisj True)
BoolDisj True
Prelude> (BoolDisj True) <> (BoolDisj False)
BoolDisj True
۸.
data Or a b =
Fst a
| Snd b
Semigroup
برای Or
باید چنین رفتاری داشته باشه. میشه فرض کنیم یه مقدار Snd
ِ چسبناک داره، یعنی اولین Snd
ای که پیدا کنه رو نگه میداره. یه چیزی شبیه مانویدی که با First'
نوشتیم.
Prelude> Fst 1 <> Snd 2
Snd 2
Prelude> Fst 1 <> Fst 2
Fst 2
Prelude> Snd 1 <> Fst 2
Snd 1
Prelude> Snd 1 <> Snd 2
Snd 1
۹.
شرکتپذیری برای Combine
رو به سادگی نمیشه تست کرد، چون شاملِ تابع هست.
newtype Combine a b =
Combine { unCombine :: (a -> b) }
کاری که باید انجام بده:
Prelude> let f = Combine $ \n -> Sum (n + 1)
Prelude> let g = Combine $ \n -> Sum (n - 1)
Prelude> unCombine (f <> g) $ 0
Sum {getSum = 0}
Prelude> unCombine (f <> g) $ 1
Sum {getSum = 2}
Prelude> unCombine (f <> f) $ 1
Sum {getSum = 4}
Prelude> unCombine (g <> f) $ 1
Sum {getSum = 2}
راهنمایی: این تابع نهایتاً به یک مقدار از تایپِ a
اعمال میشه. اما چندتا تابع خواهید داشت که همهشون یه مقدار از تایپِ b
تولید میکنن. چطور چندتا مقدار رو ترکیب کنیم تا یه b
ِ مجرد بدست بیاریم؟ این یکی شاید یه کم سخت باشه! یادتون باشه که مقداری که داخلِ Combine
هست، یه تابع ِه. اگه از CoArbitrary
سردر نمیارین، لازم نیست QuickCheck
ِش کنین.
۱۰.
newtype Comp a =
Comp { unComp :: (a -> a) }
راهنمایی: حالا که ورودی و خروجی یکی هستن، کاری رو میشه انجام داد که برای توابعْ طبیعیتر و بیشتر مختصِ اونهاست.
۱۱.
-- آشنا نیست؟
data Validation a b =
Failure a | Success b
deriving (Eq, Show)
instance Semigroup a =>
Semigroup (Validation a b) where
(<>) = undefined
۱۲.
-- که Semigroup با یه Validation
-- کار متفاوتی انجام میده
newtype AccumulateRight a b =
AccumulateRight (Validation a b)
deriving (Eq, Show)
instance Semigroup b =>
Semigroup (AccumulateRight a b) where
(<>) = undefined
۱۳.
-- که Semigroup با یه Validation
-- کار بیشتری انجام میده
newtype AccumulateBoth a b =
AccumulateBoth (Validation a b)
deriving (Eq, Show)
instance (Semigroup a, Semigroup b) =>
Semigroup (AccumulateBoth a b) where
(<>) = undefined
تمرینهای مانوید
برای نوعدادههای زیر نمونه ِ Monoid
تعریف کنین. هرجا لازم بود محدودیت ِ Monoid
به متغیرهای تایپ اضافه کنین. برای نوعدادههایی که براشون Semigroup
تعریف کردین، فقط کافیه مقدارِ همانیشون رو پیدا کنین.
۱.
باز هم همهی نمونههاتون رو با QuickCheck
تست کنین. برای تایپِ Trivial
مثال زدیم.
data Trivial = Trivial deriving (Eq, Show)
instance Semigroup Trivial where
(<>) = undefined
instance Monoid Trivial where
mempty = undefined
mappend = (<>)
type TrivAssoc =
Trivial -> Trivial -> Trivial -> Bool
main :: IO ()
main = do
let sa = semigroupAssoc
mli = monoidLeftIdentity
mri = monoidRightIdentity
quickCheck (sa :: TrivAssoc)
quickCheck (mli :: Trivial -> Bool)
quickCheck (mri :: Trivial -> Bool)
۲.
newtype Identity a =
Identity a deriving Show
۳.
data Two a b = Two a b deriving Show
۴.
newtype BoolConj =
BoolConj Bool
کاری که باید انجام بده:
Prelude> (BoolConj True) `mappend` mempty
BoolConj True
Prelude> mempty `mappend` (BoolConj False)
BoolConj False
۵.
newtype BoolDisj =
BoolDisj Bool
کاری که باید انجام بده:
Prelude> (BoolDisj True) `mappend` mempty
BoolDisj True
Prelude> mempty `mappend` (BoolDisj False)
BoolDisj False
۶.
newtype Combine a b =
Combine { unCombine :: (a -> b) }
کاری که باید انجام بده:
Prelude> let f = Combine $ \n -> Sum (n + 1)
Prelude> unCombine (mappend f mempty) $ 1
Sum {getSum = 2}
۷.
راهنمایی: حالا که ورودی و خروجی یکی هستن، کاری رو میشه انجام داد که برای توابعْ طبیعیتر و بیشتر مختصِ اونهاست.
newtype Comp a =
Comp (a -> a)
۸.
این تمرین شاید یه کم غیرطبیعی به نظر بیاد و ممکنه یه ذره سخت باشه. اگه از قبل تجربهی برنامهنویسیِ تابعی یا هسکل ندارین و تونستین این تمرین رو حل کنین، یه شیرینیِ حسابی برا خودتون بگیرین. تعریفِ اولیهی نمونه ِ Monoid
هم نوشتیم.
newtype Mem s a =
Mem {
runMem :: s -> (a,s)
}
instance Monoid a => Monoid (Mem s a) where
mempty = undefined
mappend = undefined
با توجه به کُدِ زیر:
f' = Mem $ \s -> ("hi", s + 1)
main = do
let rmzero = runMem mempty 0
rmleft = runMem (f' <> mempty) 0
rmright = runMem (mempty <> f') 0
print $ rmleft
print $ rmright
print $ (rmzero :: (String, Int))
print $ rmleft == runMem f' 0
print $ rmright == runMem f' 0
یه Monoid
ِ صحیح برای Mem
باید چنین خروجیای بده:
Prelude> main
("hi",1)
("hi",1)
("",0)
True
True
مطمئن بشین که نمونه ِ شما هم همین خروجی رو میده، البته این فقط برای خودتونه! نه اثبات ِه و نه در حدِ تست مشخصهای ِه، اما بیشترِ اشتباههای رایج رو نشون میده. اگه میخواین با ایجاد ِ توابع در QuickCheck
(نه فقط مقادیر) آشنا بشین، در مستندات ِ QuickCheck
دربارهی CoArbitrary
بخونین.
کَلَکی در کار نیست، برای s
هم تایپکلاسِ Monoid
لازم ندارین. بله، چنین Monoid
ای میتونه وجود داشته باشه. راهنمایی: مقادیرِ s
رو از یه تابع به تابع دیگه به هم زنجیر کنین. معمولاً در اولین تلاش، قانون همانی رعایت نمیشه، پس شاید بهتر باشه اول از همه قانون همانی رو چک کنین.