Haskell 入門


モナド変換子

モナド変換子

モナドは使い慣れると便利だ。例えば State モナドでは純粋関数の発想では記述不可能なスタックをプログラムすることができる。データを何度も出し入れするスタックは、変数に1回しか値を束縛できない、純粋関数の立場からはありえないからだ。

ただ、スタックを使っているときに、途中経過を表示したくなる場合がある。しかし、State モナドの do 記法の中に IO モナドの関数を記述することはできない。モナドの do 記法の中には1種類のモナドのモナド関数しか記述できないからだ。

このようなときに State モナドと IO モナドの複合モナドを作るとよい。複合モナドでは State モナドの get, put と IO モナドの print 関数を同時に使うことができるようになる。

複合モナドを作る

複合モナドを作ると言っても、State モナドに IO モナドの関数を導入することくらいなら簡単にできる。StateT モナド変換子を設定するだけだ。

StateT モナドトランスフォーマの型は StateT s m a なのでモナドの m に IO モナドを指定するだけだ。それだけのことで State モナドの中で lift $ print x の様に print x をリフトするだけで、State モナドの関数であるかのように使うことができる。嘘のような話だが、プログラム例を次に示す。

ソースコード

StateT モナド変換子を使うためには Control.Monad.State をインポートするだけでいい。ソースコードは次のようになる。

import Control.Monad.State
push :: Int -> StateT [Int] IO ()
push x = do
  xs <- get
  put (x:xs)
pop :: StateT [Int] IO Int
pop = do
  xs <- get
  put (tail xs)
  return (head xs)
main = runStateT foo []
  where foo = do
              push 10
              lift $ print 10
              push 20
              lift $ print 20
              x <- pop
              lift $ print x

スタックの push や pop 関数を StateT [Int] IO Int モナドのモナド関数として記述している。main の do 記法の中からは push や pop の操作をするたびに lift.print で経過を表示している。printf デバッグだ。

実行例は次のようになる。

*Main Control.Monad.State> main
10
20
20
((),[10])

モナド変換子を活用する高度な用法は参考書を読む必要があるが、とりあえず複合モナドはどのようなものか体験しておくのは簡単にできる。