۱۶ - ۱۲یه فانکتورِ نسبتاً حیرت‌آور

نوع‌داده‌ای هست به اسمِ ‏‎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‎‏ ِ قانونمنده.