۱۶ - ۱۲یه فانکتورِ نسبتاً حیرتآور
نوعدادهای هست به اسمِ 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
ِ قانونمنده.