۲۱ - ۱۰تمرین‌های فصل

کمی کشش و نرمش

این تمرین‌ها به عنوان دوره‌ای از چیزهایی که تو چند فصلِ آخر یاد گرفتین طراحی شدن. این تمرین‌ها اکثراً با کُدهای واقعی درست شدن، ولی یه کم ساده شدن تا تمرین‌های مجزایی باشن. اینطوری میشه تمرین‌های متمرکز روی ‏‎Traversable‎‏ و ‏‎Reader‎‏ (که هردوشون هم یه کم سخت‌اند) داشته باشیم.

اول چندتا داده ِ تمرینی درست می‌کنیم؛ توی اون برنامه‌ی اصلی که این کُد رو ازش گرفتیم، داده‌ها از جای دیگه میومدند – مثلاً یه پایگاه داده. فقط چندتا لیست عدد لازم داریم. چندتا تابع هم از ‏‎Data.Maybe‎‏ و ‏‎Control.Applicative‎‏ لازم داریم، که اولِ فایل وارد‌ِشون می‌کنیم. برای سادگی، داده‌ها ِ لیست‌مون رو با حروف رایج برای متغیرها تعریف می‌کنیم.

module ReaderPractice where

import Control.Applicative
import Data.Maybe

x = [1, 2, 3] 
y = [4, 5, 6] 
z = [7, 8, 9]

مرحله‌ی بعدی نوشتنِ تابع‌هایی‌ه که اون لیست‌ها رو به هم زیپ می‌کنن، و با استفاده از ‏‎lookup‎‏، مقدارِ منسوب به یه کلید در لیست‌های زیپ‌شده رو پیدا می‌کنن. برای تمرین خوبه که خروجی‌ها قابل پیش‌بینی باشن، پس پیشنهاد می‌کنیم اون توابع رو هم بصورت مقادیرِ معین تعریف کنین، و هم بصورت توابعی که میشه به یه متغیر اعمال بشن:

lookup :: Eq => a -> [(a, b)] -> Maybe b

-- رو با هم زیپ کنین، و با y و x
-- .استفاده کنین lookup ‎‏از تابع‏‎ 3 کلید
xs :: Maybe Integer
xs = undefined

-- رو با هم زیپ کنین، و با z و y
-- .استفاده کنین lookup ‎‏از تابع‏‎ 6 کلید
ys :: Maybe Integer
ys = undefined

-- .برگردونه Nothing خوبه یکی‌شون مثل این
-- رو با هم زیپ کنین، و با y و x
-- .استفاده کنین lookup ‎‏از تابع‏‎ 4 ‎‏کلید‏‎
zs :: Maybe Integer
zs = undefined

-- رو با هم زیپ کنین، و از کلید z و x
-- .استفاده کنین lookup متغیر برای تابع
z' :: Integer -> Maybe Integer
z' n = undefined

حالا می‌خوایم با استفاده از ‏‎Applicative‎‏، یه ‏‎Maybe (,)‎‏ از مقادیر درست کنیم. با ‏‎x1‎‏ یه توپل از ‏‎xs‎‏ و ‏‎ys‎‏، و با ‏‎x2‎‏ یه توپل از ‏‎ys‎‏ و ‏‎zs‎‏ درست کنین. ‏‎x3‎‏ هم طوری تعریف کنین که یه ورودی بگیره و از نتیجه‌ی دو بار اعمال ِ ‏‎z'‎‏ یه توپل بسازه.

x1 :: Maybe (Integer, Integer)
x1 = undefined

x2 :: Maybe (Integer, Integer)
x2 = undefined

x3 :: Integer
   -> (Maybe Integer, Maybe Integer)
x3 = undefined

خروجی‌هاتون باید این شکلی باشن:

*ReaderPractice> x1
Just (6,9)
*ReaderPractice> x2
Nothing
*ReaderPractice> x3 3
(Just 9,Just 9)

بعد چندتا تابع کمکی تعریف می‌کنیم. با استفاده از ‏‎uncurry‎‏ میشه از مقادیرِ توی یه توپل به عنوانِ آرگومان‌های یه تابع استفاده کنیم:

uncurry :: (a -> b -> c) -> (a, b) -> c
-- اون آرگومانِ اول یه تابع‌ه، که
-- .در این مورد می‌خوایم جمع باشه

-- با تابعِ uncurry میشه summed تابع
-- .جمع به عنوان اولین آرگومان‌ش
summed :: Num c => (c, c) -> c
summed = undefined

حالا یه تابع که یه تابعِ بولیَن رو از روی دوتا تابعِ نیمه اعمال‌شده لیفت می‌کنه تعریف می‌کنیم (شبیه‌ش رو قبلاً دیدیم):

bolt :: Integer -> Bool
-- ‎‏استفاده کنین.‏‎ && ، ‎‏و‏‎ >3 ، <8 ‎‏از‏‎
bolt = undefined

می‌خوایم از تابعِ ‏‎fromMaybe‎‏ هم استفاده کنیم، پس یه نگاه بهش بندازیم:

fromMaybe :: a -> Maybe a -> a

یه مقدارِ پیش‌فرض و یه مقدار ‏‎Maybe‎‏ می‌گیره. اگه مقدار ‏‎Maybe‎‏ یه ‏‎Just a‎‏ باشه، مقدار ‏‎a‎‏ رو برمی‌گردونه. اما اگه ‏‎Nothing‎‏ باشه، اون مقدار پیش‌فرض رو برمی‌گردونه:

*ReaderPractice> fromMaybe 0 xs
6
*ReaderPractice> fromMaybe 0 zs
0

حالا یه ‏‎main‎‏ سَرِهم می‌کنیم تا با یه بار صدا زدن، چندتا چیز رو اجرا کنیم:

main :: IO ()
main = do
  print $
    sequenceA [Just 3, Just 2, Just 1]
  print $ sequenceA [x, y]
  print $ sequenceA [xs, ys]
  print $ summed <$> ((,) <$> xs <*> ys)
  print $ fmap summed ((,) <$> xs <*> zs)
  print $ bolt 7
  print $ fmap bolt z

با اجرای این در REPL باید چنین جوابی بگیرین:

*ReaderPractice> main
Just [3,2,1]
[ [1,4], [1,5], [1,6]
, [2,4], [2,5], [2,6]
, [3,4], [3,5], [3,6] ]
Just [6,9]
Just 15
Nothing
True
[True,False,False]

یه خط دیگه اضافه می‌کنیم که ‏‎sequenceA‎‏ و ‏‎Reader‎‏ رو به نحو غیرمنتظره‌ای با هم ترکیب می‌کنه (این رو به ‏‎main‎‏ اضافه کنین):

print $ sequenceA [(>3), (<8), even] 7

تایپِ ‏‎sequenceA‎‏ اینه:

sequenceA :: (Applicative f, Traversable t)
          => t (f a) -> f (t a)
-- :پس اینجا
sequenceA [(>3), (<8), even] 7
-- t ~ []  و  f ~ (->) r

برای ‏‎Applicative‎‏ (منظور همون تابع‌هاست) یه ‏‎Reader‎‏ داریم، و برای اون لیست یه ‏‎Traversable‎‏. خیلی کاربردی‌ه. اسم‌ش رو میذاریم ‏‎sequA‎‏ تا باز هم ازش استفاده کنیم:

sequA :: Integral a => a -> [Bool] 
sequA m = sequenceA [(>3), (<8), even] m

از اینجا به بعد هم این بیانیه رو:

summed <$> ((,) <$> xs <*> ys)

به اسمِ ‏‎s'‎‏ انقیاد بدین.

خیلی خوب، حالا نوبت شماست. اینها رو توی ‏‎main‎‏ تعریف کنین (اگه بخواین می‌تونین هرچی بعد از ‏‎do‎‏ نوشته بودین رو پاک کنین – فقط حواستون باشه از ‏‎print‎‏ استفاده کنین تا نتایجِ چیزهایی که دارین اضافه می‌کنین رو چاپ کنین):

۱.

عملگر ِ عطف منطقی بولین رو روی لیست حاصل از ‏‎sequA‎‏ (اعمال شده به یه مقدارِ دلخواه) فولد کنین.

۲.

‏‎sequA‎‏ رو به ‏‎s'‎‏ اعمال کنین؛ ‏‎fromMaybe‎‏ لازم‌تون میشه.

۳.

‏‎bolt‎‏ رو به ‏‎ys‎‏ اعمال کنین؛ ‏‎fromMaybe‎‏ لازم‌تون میشه.