LoginSignup
5
0

More than 3 years have passed since last update.

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

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

今回は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])

今回は以上です。

5
0
1

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
5
0