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
今回はExceptTモナドです。
1. ExceptTモナドの定義
Control.Monad.Trans.Except - Hackage - Haskell
エラー処理を行うためのモナドです。
型は以下の通りです
newtype ExceptT e m a = ExceptT (m (Either e a))
runExceptT :: ExceptT e m a -> m (Either e a)
runExceptT (ExceptT m) = m
モナドの定義は以下の通りです。mの計算が失敗した場合(Left)は、計算の後続である k は呼ばれないことがポイントです。
instance (Monad m) => Monad (ExceptT e m) where
return a = ExceptT $ return (Right a)
m >>= k = ExceptT $ do
a <- runExceptT m
case a of
Left e -> return (Left e)
Right x -> runExceptT (k x)
fail = ExceptT . fail
2. 補助関数 - lift, throwE, catchE
lift は m a を ExceptT e m a に持ち上げるための関数です
lift :: Monad m => m a -> ExceptT e m a
lift = ExceptT . liftM Right
Control.Monad - Hackage - Haskell - liftM の定義は以下の通りです。
-- | Promote a function to a monad.
liftM :: (Monad m) => (a1 -> r) -> m a1 -> m r
liftM f m1 = do { x1 <- m1; return (f x1) }
-- | Signal an exception value @e@.
throwE :: (Monad m) => e -> ExceptT e m a
throwE = ExceptT . return . Left
-- | Handle an exception.
catchE :: (Monad m) =>
ExceptT e m a -- ^ the inner computation
-> (e -> ExceptT e' m a) -- ^ a handler for exceptions in the inner computation
-> ExceptT e' m a
m `catchE` h = ExceptT $ do
a <- runExceptT m
case a of
Left l -> runExceptT (h l)
Right r -> return (Right r)
#3. ExceptTの使用例 - StateTの上に乗っけてみる
以下の記事の例を拡張します。
【Control.Monad.Trans】(2) StateTモナド - Qiita
つまり ExceptT e m a モナドの m に StateT を使います。
import Data.Functor.Identity
import Control.Monad
import Control.Monad.Trans.State.Strict
import Control.Monad.Trans.Except
-- runState (runExceptT mainTasu) []
-- runState (runExceptT mainTasu) [8]
-- runState (runExceptT mainTasu) [8,7]
-- runState (runExceptT mainTasu) [8,7..0]
type Stack = [Int]
lift :: Monad m => m a -> ExceptT e m a
lift = ExceptT . liftM Right
tasu :: ExceptT String (StateT Stack Identity) Int
tasu = do
-- lift $ state $ \xs -> ((), 9:xs) -- push 9
s <- lift $ get
case s of
[] -> throwE "Error: Stack is empty !"
[_] -> throwE "Error: Stack has only one element !"
_ -> do
a <- lift $ state $ \(x:xs) -> (x, xs) -- a <- pop
b <- lift $ state $ \(x:xs) -> (x, xs) -- b <- pop
return (a+b)
mainTasu :: ExceptT String (StateT Stack Identity) Int
mainTasu = do
a <- tasu -- pointA
lift $ state $ \xs -> ((), a:xs) -- push a
return a
以下が実行例です。ExceptTのエラー処理も、StateTの状態管理も思い通り動作しているのがわかります。ExceptTモナドの定義により、Leftの場合はpointA以降の計算は行われません。逆にRightの場合は、計算結果を push a しています。以下の実行結果からLeftの場合は計算を中断しており、Rightの場合は続行していることがわかります。
*Main> runState (runExceptT mainTasu) []
(Left "Error: Stack is empty !",[])
*Main> runState (runExceptT mainTasu) [8]
(Left "Error: Stack has only one element !",[8])
*Main> runState (runExceptT mainTasu) [8,7]
(Right 15,[15])
*Main> runState (runExceptT mainTasu) [8,7..0]
(Right 15,[15,6,5,4,3,2,1,0])
今回は以上です。