3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【Control.Monad.Trans】(5) IOモナド

Last updated at Posted at 2020-01-05

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

今回はIOモナドです。

1. Monad transformersのbaseとしてのIO モナド

Control.Monad.Trans - Hackage - Haskell

Monad transformersにおいて入出力を扱いたい場合は、base モナドとしてIO モナドを利用すればいいだけです。今までのIdentityをIOに置き換えるだけです。liftIOを使えば、任意の高さまでIO aを引き上げることができます。Control.Monad.TransではliftIOとともに、liftも定義されていますので、今までのlift関数の独自定義は不要になります。

class MonadTrans t where
    lift :: Monad m => m a -> t m a

class (Monad m) => MonadIO m where
    liftIO :: IO a -> m a

2. 使用例 - 全部盛り on IO モナド

基本的にIdentityをIOに変えただけです。その上で、runState を runStateTに変え、pointBのprint分を追加しました。

import Data.Functor.Identity
import Control.Monad
import Control.Monad.Trans.State.Strict
import Control.Monad.Trans.Except
import Control.Monad.Trans.Reader
import Control.Monad.Trans

--  runStateT (runExceptT (runReaderT mainCalc '*') ) []
--  runStateT (runExceptT (runReaderT mainCalc '+') ) [8]
--  runStateT (runExceptT (runReaderT mainCalc '*') ) [8,7]
--  runStateT (runExceptT (runReaderT mainCalc '+') ) [8,7..0]


type Stack = [Int]

liftExceptT :: Monad m => m a -> ExceptT e m a
liftExceptT = ExceptT . liftM Right

liftReaderT :: m a -> ReaderT r m a
liftReaderT m = ReaderT (const m)

calc :: ReaderT Char (ExceptT String (StateT Stack IO)) Int
calc = do
  s <- liftReaderT $ liftExceptT $ get
  case s of
    []  -> liftReaderT $ throwE "Error: Stack is empty !"
    [_] -> liftReaderT $ throwE "Error: Stack has only one element !"
    _  -> do 
          liftIO $ print "Stack is good !!!"   -- pointB
          a <- liftReaderT $ liftExceptT $ state $ \(x:xs) -> (x, xs) -- a <- pop
          b <- liftReaderT $ liftExceptT $ 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 IO)) Int
mainCalc = do
  a <- calc    -- pointA
  liftReaderT $ liftExceptT $ state $ \xs -> ((), a:xs)       -- push a
  return a

以下が実行例です。ReaderTによる環境変数の受け渡しも、ExceptTのエラー処理も、StateTの状態管理も思い通り動作しているのがわかります。それに加えて出力のprint文も動作しています。

*Main> runStateT (runExceptT (runReaderT mainCalc '*') ) []
(Left "Error: Stack is empty !",[])
*Main> runStateT (runExceptT (runReaderT mainCalc '+') ) [8]
(Left "Error: Stack has only one element !",[8])
*Main> runStateT (runExceptT (runReaderT mainCalc '*') ) [8,7]
"Stack is good !!!"
(Right 56,[56])
*Main> runStateT (runExceptT (runReaderT mainCalc '+') ) [8,7..0]
"Stack is good !!!"
(Right 15,[15,6,5,4,3,2,1,0])

それではliftExceptT とliftReaderTというlift関数の独自定義を削除して、Control.Monad.Transのlift関数で置き換えます。

import Data.Functor.Identity
import Control.Monad
import Control.Monad.Trans.State.Strict
import Control.Monad.Trans.Except
import Control.Monad.Trans.Reader
import Control.Monad.Trans

--  runStateT (runExceptT (runReaderT mainCalc '*') ) []
--  runStateT (runExceptT (runReaderT mainCalc '+') ) [8]
--  runStateT (runExceptT (runReaderT mainCalc '*') ) [8,7]
--  runStateT (runExceptT (runReaderT mainCalc '+') ) [8,7..0]

type Stack = [Int]

calc :: ReaderT Char (ExceptT String (StateT Stack IO)) Int
calc = do
  s <- lift $ lift $ get
  case s of
    []  -> lift $ throwE "Error: Stack is empty !"
    [_] -> lift $ throwE "Error: Stack has only one element !"
    _  -> do 
          liftIO $ print "Stack is good !!!"
          a <- lift $ lift $ state $ \(x:xs) -> (x, xs) -- a <- pop
          b <- lift $ 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 IO)) Int
mainCalc = do
  a <- calc    -- pointA
  lift $ lift $ state $ \xs -> ((), a:xs)       -- push a
  return a

念のため実行結果を掲載します。問題ないですね。

*Main> runStateT (runExceptT (runReaderT mainCalc '*') ) []
(Left "Error: Stack is empty !",[])
*Main> runStateT (runExceptT (runReaderT mainCalc '+') ) [8]
(Left "Error: Stack has only one element !",[8])
*Main> runStateT (runExceptT (runReaderT mainCalc '*') ) [8,7]
"Stack is good !!!"
(Right 56,[56])
*Main> runStateT (runExceptT (runReaderT mainCalc '+') ) [8,7..0]
"Stack is good !!!"
(Right 15,[15,6,5,4,3,2,1,0])

今回は以上です。

3
0
0

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?