LoginSignup
4
1

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-01-04

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はそれぞれのモナドが、綺麗に積み重なって実行されていると言えるでしょう。

今回は以上です。

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1