۱۹ - ۴نمونه‌های Foldable

همونطور که گفتیم، یه نمونه ِ ‏‎Foldable‎‏ باید حداقل یکی از متودهای ‏‎foldr‎‏ یا ‏‎foldMap‎‏ رو داشته باشه. هر تابعِ دیگه از تایپکلاس رو میشه با یکی از اون دو تابع بدست آورد. با توجه به این موضوع، بریم سراغ پیاده‌سازیِ نمونه‌های ‏‎Foldable‎‏ برای تایپ‌های مختلف.

‏‎Identity‎‏

با نوشتن یه نمونه ِ ‏‎Foldable‎‏ برای ‏‎Identity‎‏ شروع می‌کنیم:

data Identity a =
  Identity a

فقط کافیه ‏‎foldr‎‏ یا ‏‎foldMap‎‏ رو بنویسیم، اما برای یادگیری هردوی اونها، بعلاوه‌ی ‏‎foldl‎‏ رو تعریف می‌کنیم.

instance Foldable Identity where
  foldr f z (Identity x) = f x z

  foldl f z (Identity x) = f z x

  foldMap f (Identity x) = f x

کاری که با ‏‎foldr‎‏ و ‏‎foldl‎‏ انجام دادیم در واقع یکی‌اند، فقط آرگومان‌هاشون جابجا شدن. برای ‏‎foldMap‎‏ هم لازم نبود کارِ خاصی انجام بدیم.

فولد کردنِ فقط یک مقدار شاید عجیب به نظر برسه. قبلاً که از کاتامورفیسم‌ها صحبت کردیم، همیشه تمرکزمون روی کاهش ِ چندتا مقدار به یک مقدار خلاصه بوده. ولی در موردِ این کاتامورفیسم ِ ‏‎Identity‎‏، محوریت بیشتر روی مصرف یا استفاده از مقداره، تا کاهش ِ مقادیرِ داخلِ ساختار به یک مقدار:

Prelude> foldr (*) 1 (Identity 5)
5
Prelude> foldl (*) 5 (Identity 5)
25

Prelude> let fm = foldMap (*5)
Prelude> type PI = Product Integer
Prelude> fm (Identity 100) :: PI
Product {getProduct = 500}

‏‎Maybe‎‏

این یکی یه ذره جذاب‌تره چون برخلافِ ‏‎Identity‎‏، باید حالت‌های ‏‎Nothing‎‏ رو هم در نظر بگیریم. وقتی مقدارِ ‏‎Maybe‎‏ ای که داریم فولد می‌کنیم ‏‎Nothing‎‏ ِه، باید بتونیم بدون انجامِ کاری با تابعِ فولدینگ، یه جور مقدارِ "صفر" برگردونیم و ساختار ِ ‏‎Maybe‎‏ رو هم حذف کنیم. این مقدارِ صفر در ‏‎foldr‎‏ و ‏‎foldl‎‏ همون مقدار اولیه‌ست:

Prelude> foldr (+) 1 Nothing
1

از طرفِ دیگه برای ‏‎foldMap‎‏ از مقدارِ همانی ِ ‏‎Monoid‎‏ به عنوانِ اون صفر استفاده می‌کنیم:

Prelude> let fm = foldMap (+1)
Prelude> fm Nothing :: Sum Integer
Sum {getSum = 0}

وقتی مقدار، یه مقدارِ ‏‎Just‎‏ ِه، باید تابع فولدینگ رو به مقدارِ داخل‌ش اعمال کنیم و ساختار رو دور بندازیم:

Prelude> foldr (+) 1 (Just 3)
4
Prelude> fm $ Just 3 :: Sum Integer
Sum {getSum = 4}

حالا خودِ نمونه رو ببینیم، باز هم برای اجتناب از تداخل با نمونه ِ ‏‎Maybe‎‏ که وجود داره، از یه تایپِ ‏‎Maybe‎‏ ِ قلابی استفاده می‌کنیم:

instance Foldable Optional where
  foldr _ z Nada = z
  foldr f z (Yep x) = f x z

  foldl _ z Nada = z
  foldl f z (Yep x) = f z x

  foldMap _ z Nada = mempty
  foldMap f z (Yep a) = f a

دقت کنین که اگه نَگین کدوم ‏‎Monoid‎‏ منظورتون بوده، از مبهم بودن تایپ ایراد می‌گیره:

Prelude> foldMap (+1) Nada

No instance for (Num a0) arising
  from a use of ‘it’
The type variable ‘a0’ is ambiguous
(...شلوغی...)

پس اگه بخوایم کار کنه باید تایپی که ‏‎Monoid‎‏ داره براش تعیین کنیم:

Prelude> import Data.Monoid
Prelude> foldMap (+1) Nada :: Sum Int
Sum {getSum = 0}
Prelude> foldMap (+1) Nada :: Product Int
Product {getProduct = 1}

Prelude> foldMap (+1) (Just 1) :: Sum Int
Sum {getSum = 2}

با مقدارِ ‏‎Nada‎‏ و تعیینِ تایپِ ‏‎Sum Int‎‏ (برای مشخص کردنِ ‏‎Monoid‎‏ تابعِ ‏‎foldMap‎‏ جوابِ ‏‎Sum 0‎‏ داد چون ‏‎mempty‎‏ یا مقدار همانی برای ‏‎Sum‎‏ ِه. بطورِ مشابه برای ‏‎Nada‎‏ و ‏‎Product‎‏، جواب ‏‎Product 1‎‏ شد چون مقدارِ همانی ِ ‏‎Product‎‏ بود.