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

【Control.Monad.Trans】(3) ExceptTモナド

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])

今回は以上です。

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