۲۵ - ۱۰تایپکلاسِ MonadIO، یا همون زوم-زوم

راه‌های زیادی برای لیفت ِ یه اجراییه از روی ساختار ِ اضافه وجود داره. ‏‎MonadIO‎‏ طراحیِ متفاوتی نسبت به ‏‎MonadTrans‎‏ داره، چون بجای اینکه لایه به لایه لیفت کنه، ‏‎MonadIO‎‏ یه اجراییه ِ ‏‎IO‎‏ رو انقدر لیفت می‌کنه تا از روی همه‌ی ساختارهای پوشونده‌شده داخلِ بیرونی‌ترین ‏‎IO‎‏ لیفت کرده باشه. هدف اینه که یه بار بنویسین ‏‎liftIO‎‏ و خودش با همه‌ی تایپ‌های زیر کار کنه:

liftIO :: IO a -> ExceptT e IO a
liftIO :: IO a -> ReaderT r IO a
liftIO :: IO a -> StateT s IO a
liftIO :: IO a -> StateT s (ReaderT r IO) a
liftIO :: IO a
       -> ExceptT
            e
            (StateT s (ReaderT r IO))
            a

اگه ‏‎Monad‎‏ ِ پایه (بیرونی‌ترین) ‏‎IO‎‏ باشه، برای رسیدن بهش لازم نیست چندبار لیفت کنین، چون ‏‎liftIO‎‏ دارین.

در کتابخونه ِ ‏‎transformers‎‏ کلاس ‏‎MonadIO‎‏ در ماژول ِ ‏‎Control.Monad.IO.Class‎‏ قرار گرفته:

class (Monad m) => MonadIO m where
  -- | یک محاسبه رو از
  -- | ‎‏لیفت کن.‏‎ IO موندِ
  liftIO :: IO a -> m a

این توضیح توی ماژول، نسبتاً خوبه، اما نقطه‌ی تمایزِ ‏‎MonadIO‎‏ از ‏‎MonadTrans‎‏ رو پررنگ نکرده:

موندهایی که ممکنه محاسباتِ ‏‎IO‎‏ درون‌شون پوشونده بشن. هر موندی که از اعمال ِ یه تسلسل از موند ترانسفورمرها به موند ِ ‏‎IO‎‏ درست شده باشه، یه نمونه از این کلاس میشه.

نمونه‌ها باید قانون‌های زیر رو (که میگن ‏‎liftIO‎‏ یه ترانسفورمر از موند‌هاست) ارضا کنن:

۱.

liftIO . return = return

۲.

liftIO (m >>= f) =
  liftIO m >>= (liftIO . f)

مثالِ ‏‎scotty‎‏ رو تغییر میدیم تا یه نوشته چاپ کنه:

{-# LANGUAGE OverloadedStrings #-}

module Main where

import Web.Scotty

import Control.Monad.IO.Class
import Data.Monoid (mconcat)

main = scotty 3000 $ do
  get "/:word" $ do
    beam <- param "word"
    liftIO (putStrLn "hello")
    html $
      mconcat ["<h1>Scotty, ",
               beam,
               " me up!</h1>"]

حالا اگه ‏‎main‎‏ رو تو REPL اجرا کنین یا یه باینری بسازین و اجرا کنین، می‌تونین از طریق مرورگر‌ِتون، از سروِر درخواستِ پاسخ کنین (همونطور که قبلاً نشون دادیم). این کار رو از طریق یه برنامه‌ی تحتِ command line مثلِ ‏‎curl‎‏ هم می‌تونین انجام بدین. اگه از مرورگر استفاده کردین و hello چند بار چاپ شده، به احتمالِ قوی مرورگر‌ِتون بیشتر از یک بار درخواست داده. اگه با ‏‎curl‎‏ امتحان کنین، نباید چنین رفتاری ببینین.

مثال از نمونه‌های ‏‎MonadIO‎‏

۱.

‏‎IdentityT‎‏

instance (MonadIO m)
      => MonadIO (IdentityT m) where
    liftIO = IdentityT . liftIO

۲.

‏‎EitherT‎‏

instance (MonadIO m)
      => MonadIO (EitherT e m) where
    liftIO = lift . liftIO

تمرین‌ها: چندتا نمونه

۱.

‏‎MaybeT‎‏

instance (MonadIO m)
      => MonadIO (MaybeT m) where
    liftIO = undefined

۲.

‏‎ReaderT‎‏

instance (MonadIO m)
      => MonadIO (ReaderT r m) where
    liftIO = undefined

۳.

‏‎StateT‎‏

instance (MonadIO m)
      => MonadIO (StateT s m) where
    liftIO = undefined

راهنمایی: نمونه‌هاتون باید ساده باشن.