۱۹ - ۵بعضی عملیات پایه‌ای

تایپکلاسِ ‏‎Foldable‎‏ شامل عملیات‌های دیگه‌ای هم میشه که هنوز چیزی ازشون نگفتیم. بعضی از اونها مثلِ ‏‎length‎‏ قبلاً فقط برای استفاده با لیست‌ها تعریف شده بودن، اما الان تعمیم داده شدن تا با بقیه‌ی انواعِ ساختارِ داده هم کاربرد داشته باشن. اینجا توصیف، تایپ سیگنچر، و مثال‌هایی از خیلی از این توابع رو نوشتیم:

-- | ،لیست المان‌های یه ساختار
-- | .از چپ به راست
toList :: t a -> [a]
Prelude> toList (Just 1)
[1]
Prelude> let xs = [Just 1, Just 2, Just 3]
Prelude> map toList xs
[[1],[2],[3]]
Prelude> concatMap toList xs
[1,2,3]

Prelude> let xs = [Just 1, Just 2, Nothing]
Prelude> concatMap toList xs
[1,2]
Prelude> toList (1, 2)
[2]

چرا اون ۱ رو نذاشت تو لیست؟ به همون دلیل که ‏‎fmap‎‏ یه تابع رو به اون ۱ اعمال نمی‌کنه.

-- | تست برای خالی بودنِ یه ساختار
null :: t a -> Bool

دقت کنین که ‏‎null‎‏ با مقادیرِ ‏‎Left‎‏ و ‏‎Nothing‎‏ هم، مثل لیستِ خالی، ‏‎True‎‏ برمی‌گردونه:

Prelude> null (Left 3)
True
Prelude> null []
True
Prelude> null Nothing
True
Prelude> null (1, 2)
False
Prelude> let xs = [Just 1, Just 2, Nothing]
Prelude> fmap null xs
[False,False,True]

تابع بعدی، ‏‎length‎‏، تعداد مقادیرِ ‏‎a‎‏ که در ‏‎t a‎‏ هستن رو برمی‌گردونه. یه لیست، به خاطرِ تعریفِ‌ش، ممکنه چندتا مقدار ‏‎a‎‏ داشته باشه. دقت کنین برای توپل‌ها، اولین آرگومان (و همینطور چپ‌ترین، یا بیرونی‌ترین آرگومان‌های تایپیِ نوع‌داده‌هایی مثل ‏‎Maybe‎‏ و ‏‎Either‎‏) جزئی از ‏‎t‎‏ به حساب میان، نه جزئی از ‏‎a‎‏.

-- | طول یا سایز یه ساختار متناهی
-- | .برمی‌گردونه Int رو به عنوان یه
length :: t a -> Int
Prelude> length (1, 2)
1
Prelude> let xs = [(1, 2), (3, 4), (5, 6)]
Prelude> length xs
3
Prelude> fmap length xs
[1,1,1]

Prelude> fmap length Just [1, 2, 3]
1

مثال آخر عجیبه، قبول داریم. اما اگه تو REPL اجراش کنین، همون جواب رو می‌گیرین. چرا؟ و چرا این:

Prelude> length $ Just [1, 2, 3]
1

همون جواب رو برمی‌گردونه؟

‏‎a‎‏ در اون مثال در ‏‎Just a‎‏ یه لیست‌ه. فقط هم یک لیست وجود داره.*

Prelude> let xs = [Just 1, Just 2, Just 3]
Prelude> fmap length xs
[1,1,1]
Prelude> let xs = [Just 1, Just 2, Nothing]
Prelude> fmap length xs
[1,1,0]
*

م. شاید این توضیح یه کم قانع‌کننده‌تر باشه: در بیانیه‌ی ‏‎fmap length Just [1,2,3]‎‏، به خاطر شرکت‌پذیری ِ چپ در اعمالِ توابع، میشه اینطور پرانتزگذاری کردِش: ‏‎(fmap length Just) [1,2,3]‎‏. جلوتر می‌بینیم که ‏‎fmap‎‏ کردنِ یه تابع روی یه تابعِ دیگه، در واقع معادل با ترکیب ِ اون دو تابع هست. پس نهایتاً میرسیم به: ‏‎(length . Just) [1,2,3]‎‏ که برابر است با: ‏‎length $ Just [1,2,3]‎‏.

-- | آیا المان در ساختار وجود داره؟
elem :: Eq a => a -> t a -> Bool

در مثال‌های پایین از ‏‎Either‎‏ استفاده کردیم تا رفتار تابع‌های ‏‎Foldable‎‏ رو با مقادیرِ ‏‎Left‎‏ نشون بدیم. همونطور که با ‏‎Functor‎‏ دیدیم، نمیشه از روی داده‌ساز ِ ‏‎Left‎‏ نگاشت کرد، چون آرگومانِ تایپیِ سمت چپ جزئی از ساختار ِه. پس در مجموعه مثال‌های پایین هم ‏‎elem‎‏ نمی‌تونه داخلِ سازنده ِ ‏‎Left‎‏ رو ببینه، در نتیجه حتی اگه مقداری که دنبال‌ش هست توی ‏‎Left‎‏ وجود داشته باشه، باز هم ‏‎False‎‏ برمی‌گردونه:

Prelude> elem 2 (Just 3)
False
Prelude> elem True (Left False)
False
Prelude> elem True (Left True)
False
Prelude> elem True (Right False)
False
Prelude> elem True (Right True)
True

Prelude> let xs = [Right 1, Right 2, Right 3]
Prelude> fmap (elem 3) xs
[False,False,True]
-- | بزرگترین المانِ یه
-- | .ساختار غیرخالی
maximum :: Ord a => t a -> a

-- | کوچکترین المان یه
-- | .ساختار غیرخالی
minimum :: Ord a => t a -> a

دقت کنین که برای این توابع، مقادیرِ ‏‎Left‎‏ و ‏‎Nothing‎‏ (و مقادیرِ مشابه) خالی محسوب میشن.

Prelude> maximum [10, 12, 33, 5]
33
Prelude> let xs = [Just 2, Just 10, Just 4]
Prelude> fmap maximum xs
[2,10,4]
Prelude> fmap maximum (Just [3, 7, 10, 2])
Just 10
Prelude> minimum "julie"
'e'
Prelude> fmap minimum (Just "julie")
Just 'e'
Prelude> let xs = map Just "jul"
Prelude> xs
[Just 'j',Just 'u',Just 'l']
Prelude> fmap minimum xs
"jul"

Prelude> let xs = [Just 4, Just 3, Nothing]
Prelude> fmap minimum xs
[4,3,*** Exception: minimum: empty structure
Prelude> minimum (Left 3)
*** Exception: minimum: empty structure

توابعِ ‏‎sum‎‏ و ‏‎product‎‏ رو قبلاً دیدیم و از روی اسم‌شون هم میشه گفت چه کاری انجام میدن: جمع و ضرب ِ اعضای یه ساختار رو برمی‌گردونن:

sum :: (Foldable t, Num a) => t a -> a

product :: (Foldable t, Num a) => t a -> a

حالا چندتا مثال:

Prelude> sum (7, 5)
5
Prelude> fmap sum [(7, 5), (3, 4)]
[5,4]
Prelude> fmap sum (Just [1, 2, 3, 4, 5])
Just 15
Prelude> product Nothing
1
Prelude> fmap product (Just [])
Just 1
Prelude> fmap product (Right [1, 2, 3])
Right 6

تمرین‌ها: تابع‌های کتابخونه

توابع رو برمبنای ‏‎foldMap‎‏ یا ‏‎foldr‎‏ از ‏‎Foldable‎‏ تعریف کنین، و بعد با چندتا تایپ که نمونه ِ ‏‎Foldable‎‏ دارن امتحان کنین.

۱.

این و بعدی با ‏‎foldMap‎‏ بهتر میشن، اما ‏‎foldr‎‏ هم خوبه.

sum :: (Foldable t, Num a) => t a -> a

۲.

product :: (Foldable t, Num a) => t a -> a

۳.

elem :: (Foldable t, Eq a)
     => a -> t a -> Bool

۴.

minimum :: (Foldable t, Ord a)
        => t a -> Maybe a

۵.

maximum :: (Foldable t, Ord a)
        => t a -> Maybe a

۶.

null :: (Foldable t) => t a -> Bool

۷.

length :: (Foldable t) => t a -> Int

۸.

-- بعضی‌ها این رو اصل قضیه
-- می‌دونن Foldable برای
toList :: (Foldable t) => t a -> [a]

۹.

-- .استفاده کنین foldMap راهنمایی: از

-- | المان‌های یه ساختار رو با استفاده
-- | .از یه مانوید ترکیب کنین
fold :: (Foldable t, Monoid m) => t m -> m

۱۰.

-- .تعریف کنین foldr رو با foldMap
foldMap :: (Foldable t, Monoid m)
        => (a -> m) -> t a -> m