۲۵ - ۸بیرونی‌ترین ساختار، داخلی‌ترین واژه‌ست

یکی از مواردِ سختِ موند ترانسفورمرها اینه که ارائه‌ی واژگانی ِ تایپ‌ها در رابطه با ساختار ِ مقادیر، برخلاف چیزیه که در نگاه اول به نظر میاد. اجازه بدین به نکته‌ای در تعاریفِ این تایپ‌ها اشاره کنیم:

-- transformers این تعاریف در کتابخونه‌ی
-- ‎‏ممکنه کمی فرق داشته باشن. مهم نیست.‏‎
newtype ExceptT e m a =
  ExceptT { runExceptT :: m (Either e a) }

newtype MaybeT m a =
  MaybeT { runMaybeT :: m (Maybe a) }

newtype ReaderT r m a =
  ReaderT { runReaderT :: r -> m a }

یکی از مشخصه‌های واجب در ترانسفورمرها اینه که ساختار ِ اضافیِ ‏‎m‎‏ همیشه دور مقدار رو پوشونده. به این هم دقت کنین که اون ساختار همیشه چیزی رو پوشونده که در اختیار داریم، نه چیزی که لازم داریم، مثلِ ‏‎ReaderT‎‏. پیآمدِ چنین چیزی اینه که یک سری از موند ترانسفورمرها در یه تایپ، از داخلی‌ترین تایپ (از لحاظِ ساختاری) شروع میشه. مثال زیر رو در نظر بگیرین:

module OuterInner where

import Control.Monad.Trans.Except
import Control.Monad.Trans.Maybe
import Control.Monad.Trans.Reader

-- استفاده return فقط کافیه یکبار از
-- ‎‏گنده داریم.‏‎ Monad کنیم، چون فقط یه
embedded :: MaybeT
            (ExceptT String
                     (ReaderT () IO))
            Int
embedded = return 1

میشه لایه‌لایه "پوست" بِکَنیم:

maybeUnwrap :: ExceptT String
               (ReaderT () IO) (Maybe Int)
maybeUnwrap = runMaybeT embedded

-- بعدی
eitherUnwrap :: ReaderT () IO
                (Either String (Maybe Int))
eitherUnwrap = runExceptT maybeUnwrap

-- و در آخر
readerUnwrap :: ()
             -> IO (Either String
                           (Maybe Int))
readerUnwrap = runReaderT eitherUnwrap

بعد اگه بخوایم این کُد رو محاسبه کنیم، مقدارِ واحد رو به تابع میدیم:

Prelude> readerUnwrap ()
Right (Just 1)

چرا این جواب؟ دقت کنین که از ‏‎return‎‏ برای یه ‏‎Monad‎‏ متشکل از ‏‎Reader‎‏، ‏‎Either‎‏، و ‏‎Maybe‎‏ استفاده کردیم:

instance Monad ((->) r) where
  return = const

instance Monad (Either e) where
  return = Right

instance Monad Maybe where
  return = Just

میشه استفاده از ‏‎return‎‏ برای دسته ِ ‏‎Reader‎‏/‏‎Either‎‏/‏‎Maybe‎‏ رو به چشمِ ترکیب نگاه کنیم. ببینید چطور کُدِ زیر همون جوابِ ‏‎readerUnwrap ()‎‏ رو میده:

Prelude> (const . Right . Just $ 1) ()
Right (Just 1)

یه نکته‌ی که در خوندنِ موند ترانسفورمرها باید به خاطر داشته باشین اینه که وقتی هسکل‌نویس‌ها میگن موندِ پایه، معمولاً منظورشون بیرونی‌ترین از لحاظِ ساختاری ِه.

type MyType a = IO [Maybe a]

در ‏‎MyType‎‏، موندِ پایه میشه ‏‎IO‎‏.

تمرین: بپیچونین

تابعِ ‏‎readerUnwrap‎‏ از مثالِ قبل رو با استفاده از داده‌ساز‌های هر ترانسفورمر به ‏‎embedded‎‏ برگردونین.

-- ‎‏تغییرش بدین تا کار کنه.‏‎

embedded :: MaybeT
            (ExceptT String
                     (ReaderT () IO))
            Int
embedded = ??? (const (Right (Just 1)))