۱۶ - ۱۲یه فانکتورِ نسبتاً حیرتآور
نوعدادهای هست به اسمِ Const یا Constant – بسته به کتابخونهای که ازش استفاده میکنین، ممکنه هردو رو ببینین. Constant یه Functor ِ معتبر داره، اما رفتارِ نمونه ِ Functor ممکنه یه کم سورپرایزِتون کنه. اول یه نگاه به تابعِ const بندازیم، بعد نوعداده رو میبینیم:
Prelude> :t const
const :: a -> b -> a
Prelude> let a = const 1
Prelude> a 1
1
Prelude> a 2
1
Prelude> a 3
1
Prelude> a "blah"
1
Prelude> a id
1نوعداده ِ Constant هم مفهوم مشابهی داره. Constant این شکلیه:
newtype Constant a b where
Constant { getConstant :: a }
deriving (Eq, Show)چیزی که میشه دید، خیالی بودنِ تایپِ b هست. هیچ شاهدی در سطحِ جملهای (یا سطحِ مقداری) نداره. این یه تاکتیک و ایدهایه که بعداً بیشتر بررسی میکنیم، الان میشه دید چطور کارِ مشابهِ تابعِ const رو انجام میده:
Prelude> Constant 2
Constant {getConstant = 2}با اینکه b یه تایپ خیالی ِه، Constant گونه ِ * -> * -> * داره، و یه Functor ِ قابل قبول نیست. پس چطوری براش نمونه بنویسیم؟ خوب، یه کاری هست که هم با تابعها میشه انجام داد، و هم با نوعسازها: اعمالِشون کنیم. پس یه Functor برای Constant a داریم، اما برای Constant به تنهایی نداریم. باید Constant a باشه و Constant a b نباشه چون کایند ِ Constant a b میشه *.
تعریفِ Functor برای Constant رو ببینیم:
instance Functor (Constant m) where
fmap _ (Constant v) = Constant vشبیهِ همانی میمونه، نه؟ تو REPL قوانینِ Functor رو براش بررسی میکنیم:
Prelude> const 2 (getConstant (Constant 3))
2
Prelude> fmap (const 2) (Constant 3)
Constant {getConstant = 3}
Prelude> let gc = getConstant
Prelude> let c = Constant 3
Prelude> gc $ fmap (const 2) c
3
Prelude> gc $ fmap (const "blah") c
3وقتی تابعِ const رو fmap میکنیم، آرگومان اولِ const هیچوقت استفاده نمیشه، چون خودِ const ِ نیمه اعمالشده هم اصلاً استفاده نمیشه. اولین آرگومان تایپی برای نوعساز ِ Constant، بخشی از اون ساختاریه که Functor از روش رَد میشه و نادیده میگیره. آرگومانِ دومِ نوعساز ِ Constant هم، متغیرِ تایپ ِ خیالی ِ b هست که مقدار یا شاهد ِ سطح جملهای نداره. از اونجا که اون تایپی که Functor قراره روش نگاشت کنه هیچ مقداری نداره، هیچ چیزی نداریم که بشه تابع رو بهش اعمال کنیم، پس هیچ وقت از بیانیهی const استفاده نمیکنیم.
اما آیا از قوانینِ Functor پیروی میکنه؟
-- تستِ همانی
Prelude> getConstant (id (Constant 3))
3
Prelude> getConstant (fmap id (Constant 3))
3
-- const ترکیب تابع
Prelude> ((const 3) . (const 5)) 10
3
Prelude> ((const 5) . (const 3)) 10
5
-- ترکیبپذیری
Prelude> fc = fmap (const 3)
Prelude> fc' = fmap (const 5)
Prelude> let separate = fc . fc'
Prelude> let c = const 3
Prelude> let c' = const 5
Prelude> let fused = fmap (c . c')
Prelude> let cw = Constant "WOOHOO"
Prelude> getConstant $ separate $ cw
"WOOHOO"
Prelude> let cdr = Constant "Dogs rule"
Prelude> getConstant $ fused $ cdr
"Dogs rule"(Constant a) گونه ِ * -> * داره که برای Functor درسته، اما اینجا نگاشت روی b انجام میشه، نه a.
کاری که بالا کردیم در حدِ یه تست اجمالی بود، نه یه اثبات برای معتبر بودنِ Functor. بیشترِ برنامهنویسها از همین اثباتهای نیمهکاره که بین سیاهی و سفیدی قرار گرفتن استفاده میکنن. با اینکه یه کم بیهوده به نظر میرسه، Constant یه Functor ِ قانونمنده.