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