۱۲ - ۵تمرینهای فصل
کایندها رو تشخیص بدین
۱.
کایند ِ a چیه؟
id :: a -> a۲.
کایندهای a و f چیاند؟
r :: a -> f aپردازشِ نوشته
این تمرین از اونجور کارهایی ِه که زبانشناسها برا سرگرمی انجام میدن.
۱.
یه تابعِ بازگشتی به اسمِ replaceWith بنویسین که یه String میگیره، لغاتش رو جدا میکنه و هر جا the هست، با a جایگزین میکنه. این تابع قراره فقط the رو جایگزین کنه. تابعِ notThe یه تابعِ کمکی ِ پیشنهادیه.
-- GHCi نمونه جلسهی
-- >>> notThe "the"
-- Nothing
-- >>> notThe "blahtheblah"
-- Just "blahtheblah"
-- >>> notThe "woot"
-- Just "woot"
notThe :: String -> Maybe String
notThe = undefined
-- >>> replaceThe "the cow loves us"
-- "a cow loves us"
replaceThe :: String -> String
replaceThe = undefined۲.
یه تابعِ بازگشتی بنویسین که یه String میگیره، لغاتش رو جدا میکنه و تعداد دفعاتی که بعد از the یه کلمه که با حرف صدادار شروع میشه اومده رو میشماره.
-- >>> countTheBeforeVowel "the cow"
-- 0
-- >>> countTheBeforeVowel "the evil cow"
-- 1
countTheBeforeVowel :: String -> Integer
countTheBeforeVowel = undefined۳.
تعداد حروف صدادار در یه کلمه رو بشمارین.
راهنمایی: اگه مسئله رو ریز ریز کنین بهتون کمک میکنه. هر چقدر تابعِ کمکی لازم دارین بنویسین.
a)
صدادار بودن رو بررسی کنین.
b)
حروف صدادارِ یه نوشته رو برگردونین.
c)
تعداد المانهای خروجی رو بشمارین.
-- >>> countVowels "the cow"
-- 2
-- >>> countVowels "Mikolajczak"
-- 4
countVowels :: String -> Integer
countVowels = undefinedکلمه رو تأیید کنین
با استفاده از تایپِ Maybe تابعی بنویسین که تعدادِ حروف صدادار و حروف بیصدا در یه نوشته رو بشماره. اگه تعداد حروف صدادار از حروف بیصدا بیشتر شد، تابع باید Nothing برگردونه. در اکثر زبانها خیلی کم پیش میاد حروف صدادار از حروف بیصدا در یه لغت بیشتر باشن، پس هر وقت این اتفاق بیوفته، شاید ورودی لغت نباشه (یا به عبارتِ دیگه، ورودیِ معتبری نباشه):
newtype Word' =
Word' String
deriving (Eq, Show)
vowels = "aeiou"
mkWord :: String -> Maybe Word'
mkWord = undefinedپیش میاد، طبیعیه
یه نوعداده برای ارائهی اعدادِ طبیعی بهتون میدیم. تنها اعدادی که با اعدادِ طبیعی میشه نشون داد اعدادِ صفر تا بینهایتاند. برای این تمرین توابعی بنویسین که Natural ها رو به Integer، و Integerها رو به Natural تبدیل میکنن. تبدیلِ Natural به Integer لازم نیست Maybe برگردونه، چون Integer سوپرمجموعه ِ Natural ِه. هر Natural ای رو میشه با Integer نشون داد، اما برعکسش برای هر Integer ای صادق نیست.
-- طبیعی، مثل هیکل
-- یه بدنساز حرفهای
data Nat =
Zero
| Succ Nat
deriving (Eq, Show)
-- >>> natToInteger Zero
-- 0
-- >>> natToInteger (Succ Zero)
-- 1
-- >>> natToInteger (Succ (Succ Zero))
-- 2
natToInteger :: Nat -> Integer
natToInteger = undefined
-- >>> integerToNat 0
-- Just Zero
-- >>> integerToNat 1
-- Just (Succ Zero)
-- >>> integerToNat 2
-- Just (Succ (Succ Zero))
-- >>> integerToNat (-1)
-- Nothing
integerToNat :: Integer -> Maybe Nat
integerToNat = undefinedیه کتابخونه ِ کوچیک برای Maybe
توابعِ زیر رو تعریف کنین. شاید یه کم وقت ببره.
۱.
بررسیِ بولیَن برای مقادیرِ Maybe.
-- >>> isJust (Just 1)
-- True
-- >>> isJust Nothing
-- False
isJust :: Maybe a -> Bool
-- >>> isNothing (Just 1)
-- False
-- >>> isNothing Nothing
-- True
isNothing :: Maybe a -> Bool۲.
کاتامورفیسم ِ Maybe. هر مقدارِ Maybe رو به میتونه به هر چیزِ دیگه تبدیل کنه.
-- >>> mayybee 0 (+1) Nothing
-- 0
-- >>> mayybee 0 (+1) (Just 1)
-- 2
mayybee :: b -> (a -> b) -> Maybe a -> b۳.
برای مواقعی که فقط یه مقدارِ پشتیبان لازم دارین.
-- >>> fromMaybe 0 Nothing
-- 0
-- >>> fromMaybe 0 (Just 1)
-- 1
fromMaybe :: a -> Maybe a -> a
-- سعی کنین با استفاده از
-- بنویسین maybe کاتامورفیسم۴.
تبدیلِ لیست به Maybe و برعکس.
-- >>> listTMaybe [1, 2, 3]
-- Just 1
-- >>> listToMaybe []
-- Nothing
listToMaybe :: [a] -> Maybe a
-- >>> maybeToList (Just 1)
-- [1]
-- >>> maybeToList Nothing
-- []
maybeToList :: Maybe a -> [a]۵.
حذفِ مقادیرِ Nothing از لیستِ ورودی.
-- >>> catMaybes [Just 1, Nothing, Just 2]
-- [1, 2]
-- >>> let xs = take 3 $ repeat Nothing
-- >>> catMaybes xs
-- []
catMaybes :: [Maybe a] -> [a]۶.
بعداً این تابع رو به اسمِ sequence میبینین.
-- >>> flipMaybe [Just 1, Just 2, Just 3]
-- Just [1, 2, 3]
-- >>> flipMaybe [Just 1, Nothing, Just 3]
-- Nothing
flipMaybe :: [Maybe a] -> Maybe [a]یه کتابخونه ِ کوچیک برای Either
هر کدوم از توابعِ زیر رو بنویسین. اگه برای هر تایپ بیشتر از یک تابعِ یکتا وجود داشت، خودتون قضاوت کنین چه کاری باید انجام بده.
۱.
تابعِ زیر رو هرطور خواستین تعریف کنین، ولی سعی کنین بالاخره به جوابی برسین که با foldr نوشته شده.
lefts' :: [Either a b] -> [a]۲.
مثلِ تمرینِ قبل. باز هم سعی کنین به foldr برسین.
rights' :: [Either a b] -> [b]۳.
partitionEithers' :: [Either a b]
-> ([a], [b])۴.
eitherMaybe' :: (b -> c)
-> Either a b
-> Maybe c۵.
این تابع یه کاتامورفیسم ِ عمومی برای مقادیرِ Either ِه.
either' :: (a -> c)
-> (b -> c)
-> Either a b
-> c۶.
مثلِ تمرینِ ۴، فقط اینبار با استفاده از either' که الان تعریف کردین بنویسین.
eitherMaybe'' :: (b -> c)
-> Either a b
-> Maybe cبیشترِ توابعی که الان دیدین، در Prelude، Data.Maybe، یا Data.Either وجود دارن، اما باید سعی کنین بدونِ نگاه کردن به تعاریفِ اونها خودتون بنویسین. اگه تقلب کنین، به خودتون کملطفی کردین.
آنفولد
با اینکه تازه کاتامورفیسمها رو یاد گرفتیم، یه نگاه به دوگان ِ اونها، یعنی آنامورفیسمها بندازیم. با فولد (یا کاتامورفیسم) میشد ساختارهای داده رو تخریب کنیم، در مقابل با آنامورفیسم میشه اونها رو بسازیم. مثلِ فولدها، چند راه برای آنفولد کردنِ یه ساختار داده وجود داره. هم میشه ساختارهای دادهی متناهی بسازیم، و هم نامتناهی.
-- مثل یه آنفولدِ محدود iterate تابع
-- میمونه که هیچوقت تموم نمیشه
Prelude> :t iterate
iterate :: (a -> a) -> a -> [a]
-- به همین خاطر که تمومی نداره،
-- یه لیست متناهی بگیریم take باید با
Prelude> take 10 $ iterate (+1) 0
[0,1,2,3,4,5,6,7,8,9]
-- جامعتره unfoldr تابع
Prelude> :t unfoldr
unfoldr :: (b -> Maybe (a, b)) -> b -> [a]
-- برای unfoldr استفاده از
-- iterate بازنویسی
Prelude> take 10 $ unfoldr (\b -> Just (b, b+1)) 0
[0,1,2,3,4,5,6,7,8,9]خب چه فایده؟
این همون فایده رو داره که با فولد، خوداتکایی ِ مستقیم رو در تابعهایی مثل sum، product، و concat انتزاعی کردیم.
import Data.List
mehSum :: Num a => [a] -> a
mehSum xs = go 0 xs
where go :: Num a => a -> [a] -> a
go n [] = n
go n (x:xs) = (go (n+x) xs)
niceSum :: Num a => [a] -> a
niceSum = foldl' (+) 0
mehProduct :: Num a => [a] -> a
mehProduct xs = go 1 xs
where go :: Num a => a -> [a] -> a
go n [] = n
go n (x:xs) = (go (n*x) xs)
niceProduct :: Num a => [a] -> a
niceProduct = foldl' (*) 1
mehConcat :: [[a]] -> [a]
mehConcat xs = go [] xs
where go :: Num a => a -> [a] -> a
go xs' [] = xs'
go xs' (x:xs) = (go (xs' ++ x) xs)
niceConcat :: [[a]] -> [a]
niceConcat = foldr (++) []شاید سردرد گرفته باشین، اما شاید هم متوجه شده باشین که اینطور انتزاعی کردنِ الگوهای رایج و اسم دادن به اونها، همونطور که برای فولدها فایده داره برای آنفولدها هم فایده داره.
نسخهی iterate و unfoldr ِ خودتون رو بنویسین
۱.
تابعِ myIterate رو با خوداتکایی ِ مستقیم بنویسین. برای اطمینان از صحت جوابتون با iterate ِ موجود مقایسه کنین. تعریفِ تابعِ iterate در منبع، یا مثالهای مشابهش رو نخونین که مجبور شین خودتون حل کنین.
myIterate :: (a -> a) -> a -> [a]
myIterate = undefined۲.
تابعِ myUnfoldr رو با خوداتکایی ِ مستقیم بنویسین. برای اطمینان از صحتِ جوابتون با unfoldr ِ موجود مقایسه کنین. باز هم تعاریفِ unfoldr رو نخونین تا خودتون حل کنین.
myUnfoldr :: (b -> Maybe (a, b))
-> b
-> [a]
myUnfoldr = undefined۳.
تابعِ myIterate رو با استفاده از myUnfoldr به اسمِ betterIterate بازنویسی کنین. یه راهنمایی – بالاتر، از unfoldr استفاده کردیم تا همون جوابِ iterate رو بدست بیاریم. همین کار رو با توابع مختلف انجام بدین و ببینین آیا میتونین ساختار رو انتزاعی کنین.
-- تایپ جلوی چشمتون باشه کمک میکنه
-- myUnfoldr :: (b -> Maybe (a, b))
-- -> b
-- -> [a]
betterIterate :: (a -> a) -> a -> [a]
betterIterate f x = myUnfoldr ...?یادتون باشه که betterIterate باید همون جوابِ iterate رو بده.
Prelude> take 10 $ iterate (+1) 0
[0,1,2,3,4,5,6,7,8,9]
Prelude> take 10 $ betterIterate (+1) 0
[0,1,2,3,4,5,6,7,8,9]بالاخره یه چیزی غیر از لیست!
با BinaryTree از فصلِ قبل، تمرینهای زیر رو کامل کنین. اینجا دوباره نوعداده رو نوشتیم:
data BinaryTree a =
Leaf
| Node (BinaryTree a) a (BinaryTree a)
deriving (Eq, Ord, Show)۱.
برای BinaryTree تابعِ unfold بنویسین.
unfold :: (a -> Maybe (a,b,a))
-> a
-> BinaryTree b
unfold = undefined۲.
یه درختساز تعریف کنین. با استفاده از تابعِ unfold که برای BinaryTree تعریف کردین، تابعِ زیر رو بنویسین:
treeBuild :: Integer -> BinaryTree Integer
treeBuild = undefinedچنین جوابهایی باید بده:
Prelude> treeBuild 0
Leaf
Prelude> treeBuild 1
Node Leaf 0 Leaf
Prelude> treeBuild 2
Node (Node Leaf 1 Leaf)
0
(Node Leaf 1 Leaf)
Prelude> treeBuild 3
Node (Node (Node Leaf 2 Leaf)
1
(Node Leaf 2 Leaf))
0
(Node (Node Leaf 2 Leaf)
1
(Node Leaf 2 Leaf))یا یه جور دیگه نشون بدیم:
0
0
/ \
1 1
0
/ \
1 1
/\ /\
2 2 2 2آفرین.