۲۵ - ۸بیرونیترین ساختار، داخلیترین واژهست
یکی از مواردِ سختِ موند ترانسفورمرها اینه که ارائهی واژگانی ِ تایپها در رابطه با ساختار ِ مقادیر، برخلاف چیزیه که در نگاه اول به نظر میاد. اجازه بدین به نکتهای در تعاریفِ این تایپها اشاره کنیم:
-- 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)))