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

[Haskell/Monad transformers](https://en.wikibooks.org/wiki/Haskell/Monad_transformers)
[A Gentle Introduction to Monad Transformers](https://github.com/kqr/gists/blob/master/articles/gentle-introduction-monad-transformers.md)
[モナドトランスフォーマー・ステップ・バイ・ステップ](http://bicycle1885.hatenablog.com/entry/2012/12/08/165236)

---

[【Control.Monad.Trans】(1) Identityモナド - Qiita](https://qiita.com/sand/items/e88b798d8783606071c5)
[【Control.Monad.Trans】(2) StateTモナド - Qiita](https://qiita.com/sand/items/802b8c4a8ae19f04102b)
[【Control.Monad.Trans】(3) ExceptTモナド - Qiita](https://qiita.com/sand/items/fe3c29560136803e8b11)
[【Control.Monad.Trans】(4) ReaderTモナド - Qiita](https://qiita.com/sand/items/93d6ba75b0c4e39ce295)
[【Control.Monad.Trans】(5) IOモナド - Qiita](https://qiita.com/sand/items/ee3c19c191c87c32f867)


Monad transformersを中心に、モナドの基本的な事項をまとめてみました。(1)～(5)までの連載で、Monad transformersを試行していきます。実際にはそれぞれの回で、Identity, StateT, ExceptT, ReaderT, IO モナドを積み重ねていく例題を示しています。

Monad transformersについては、以下のサイトが表向きは最新といわれるものでしょうが、中身はControl.Monad.Transの方をimportしたりしているので、説明の都合上、Control.Monad.Transを使います。
[mtl - Hackage - Haskell](https://hackage.haskell.org/package/mtl)



今回はExceptTモナドです。


# 1. ExceptTモナドの定義

[ Control.Monad.Trans.Except - Hackage - Haskell](http://hackage.haskell.org/package/transformers-0.5.6.2/docs/Control-Monad-Trans-Except.html)

エラー処理を行うためのモナドです。

型は以下の通りです

```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 は呼ばれないことがポイントです。

```haskell:
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 に持ち上げるための関数です

```Haskell:
lift :: Monad m => m a -> ExceptT e m a
lift = ExceptT . liftM Right
```

[Control.Monad - Hackage - Haskell](http://hackage.haskell.org/package/base-4.12.0.0/docs/Control-Monad.html) - liftM の定義は以下の通りです。


```Haskell:
-- | Promote a function to a monad.
liftM   :: (Monad m) => (a1 -> r) -> m a1 -> m r
liftM f m1              = do { x1 <- m1; return (f x1) }
```


```Haskell:
-- | 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](https://qiita.com/sand/items/802b8c4a8ae19f04102b)


つまり　ExceptT e m a　モナドの m に StateT を使います。


```Haskell:
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])
```


今回は以上です。