۱۵ - ۱۵تمرینهای فصل
تمرینهای نیمگروه
برای نوعدادههای زیر نمونه ِ 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 bSemigroup برای 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 رو از یه تابع به تابع دیگه به هم زنجیر کنین. معمولاً در اولین تلاش، قانون همانی رعایت نمیشه، پس شاید بهتر باشه اول از همه قانون همانی رو چک کنین.