Help us understand the problem. What is going on with this article?

【Control.Monad.Trans】(4) ReaderTモナド

Haskell/Monad transformers
A Gentle Introduction to Monad Transformers
モナドトランスフォーマー・ステップ・バイ・ステップ


【Control.Monad.Trans】(1) Identityモナド - Qiita
【Control.Monad.Trans】(2) StateTモナド - Qiita
【Control.Monad.Trans】(3) ExceptTモナド - Qiita
【Control.Monad.Trans】(4) ReaderTモナド - Qiita
【Control.Monad.Trans】(5) IOモナド - Qiita

Monad transformersを中心に、モナドの基本的な事項をまとめてみました。(1)~(5)までの連載で、Monad transformersを試行していきます。実際にはそれぞれの回で、Identity, StateT, ExceptT, ReaderT, IO モナドを積み重ねていく例題を示しています。

Monad transformersについては、以下のサイトが表向きは最新といわれるものでしょうが、中身はControl.Monad.Transの方をimportしたりしているので、説明の都合上、Control.Monad.Transを使います。
mtl - Hackage - Haskell

今回はReaderTモナドです。

1. ReaderTモナドの定義

Control.Monad.Trans.Reader - Hackage - Haskell

ReaderTモナドは設定ファイルを読み込むような感じで、環境変数を受け継いでいく機能を提供します。

型は以下の通りです

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

モナドの定義は以下の通りです。

instance (Monad m) => Monad (ReaderT r m) where
    return   = lift . return   -- 右辺のreturn :: r -> m r
    m >>= k  = ReaderT $ \ r -> do
        a <- runReaderT m r
        runReaderT (k a) r
    (>>) = (*>)
    m >> k = ReaderT $ \ r -> runReaderT m r >> runReaderT k r
    fail msg = lift (fail msg)

2. 補助関数 - lift, ask

lift (liftReaderT) は m a を ReaderT r m a に持ち上げるための関数です。
ask は現在の環境の値を取得します。

-- | Fetch the value of the environment.
ask :: (Monad m) => ReaderT r m r
ask = ReaderT return

instance MonadTrans (ReaderT r) where
    lift   = liftReaderT

liftReaderT :: m a -> ReaderT r m a
liftReaderT m = ReaderT (const m)     -- (const m) :: r -> m a

3. ReaderTの使用例 - ExceptTとStateTの上に乗っけてみる

以下の記事の例を拡張します。
【Control.Monad.Trans】(3) ExceptTモナド - Qiita

つまり ReaderT r m a モナドの m に ExceptT を使います。環境変数の値として掛け算か足し算を表す '*' or '+' を渡すようにします。 

import Data.Functor.Identity
import Control.Monad
import Control.Monad.Trans.State.Strict
import Control.Monad.Trans.Except
import Control.Monad.Trans.Reader

--  runState (runExceptT (runReaderT mainCalc '*') ) []
--  runState (runExceptT (runReaderT mainCalc '+') ) [8]
--  runState (runExceptT (runReaderT mainCalc '*') ) [8,7]
--  runState (runExceptT (runReaderT mainCalc '+') ) [8,7..0]


type Stack = [Int]

lift :: Monad m => m a -> ExceptT e m a
lift = ExceptT . liftM Right

liftReaderT :: m a -> ReaderT r m a
liftReaderT m = ReaderT (const m)

calc :: ReaderT Char (ExceptT String (StateT Stack Identity)) Int
calc = do
  s <- liftReaderT $ lift $ get
  case s of
    []  -> liftReaderT $ throwE "Error: Stack is empty !"
    [_] -> liftReaderT $ throwE "Error: Stack has only one element !"
    _  -> do 
          a <- liftReaderT $ lift $ state $ \(x:xs) -> (x, xs) -- a <- pop
          b <- liftReaderT $ lift $ state $ \(x:xs) -> (x, xs) -- b <- pop
          op <- ask
          case op of
            '*' -> return (a*b) 
            '+' -> return (a+b)
            _   -> return 0

mainCalc :: ReaderT Char (ExceptT String (StateT Stack Identity)) Int
mainCalc = do
  a <- calc    -- pointA
  liftReaderT $ lift $ state $ \xs -> ((), a:xs)       -- push a
  return a

以下が実行例です。ReaderTによる環境変数の受け渡しも、ExceptTのエラー処理も、StateTの状態管理も思い通り動作しているのがわかります。

*Main> runState (runExceptT (runReaderT mainCalc '*') ) []
(Left "Error: Stack is empty !",[])
*Main> runState (runExceptT (runReaderT mainCalc '+') ) [8]
(Left "Error: Stack has only one element !",[8])
*Main> runState (runExceptT (runReaderT mainCalc '*') ) [8,7]
(Right 56,[56])
*Main> runState (runExceptT (runReaderT mainCalc '+') ) [8,7..0]
(Right 15,[15,6,5,4,3,2,1,0])
*Main> runState (runExceptT (runReaderT mainCalc '*') ) [8,7..0]
(Right 56,[56,6,5,4,3,2,1,0])

ReaderTとStateTの引数は、それぞれのrunレベルにおいて与えていることに注意してください。Monad transformersはそれぞれのモナドが、綺麗に積み重なって実行されていると言えるでしょう。

今回は以上です。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away