猿でも持ち上げられるモナド

最近やっとモナドの持ち上げ方を理解した気分になりました。StateT + IO 限定で。

State モナドを使って以下のようなコードを書いていたとき、

module Main(main) where
import Control.Monad.State

-- 階乗計算
fact :: State (Int, Int) Int
fact = do
    (a, b) <- get
    -- (A)
    if a == 0
        then return b
        else do
            modify $ \(a, b) -> (a-1, a*b)
            fact

main :: IO ()
main = do
    let x = evalState fact (10, 1)
    print x

コメントの (A) の位置で (a, b) の値を表示したくなったときに使える技です。

module Main(main) where
import Control.Monad.State

-- 階乗計算
fact :: StateT (Int, Int) IO Int -- (1)
fact = do
    (a, b) <- get
    liftIO $ print (a, b) -- (2)
    if a == 0
        then return b
        else do
            modify $ \(a, b) -> (a-1, a*b)
            fact

main :: IO ()
main = do
    x <- evalStateT fact (10, 1) -- (3)
    print x

変更箇所はたった 3 箇所。

  • (1) fact の型を State a b から StateT a IO b に書き換える (そもそも fact に型注釈してなければ必要ない) 。
  • (2) (A) の箇所に好きな IO モナドを書く。ただしその前に liftIO をつける。
  • (3) fact の型変更に合わせて呼び出しを変える。


以下愚痴。「モナドのすべて」 (に限らず Haskell の文献全般) の説明は、「いきなり定義を言って、無駄にややこしい例を出して、終わり」というのが多くて悲しいです*1。1 を聞いて 10 を知るというか、ペアノ算術を聞いて 10 を知るような人ならすぐに理解するんでしょうけどねえ。

*1:ややこしい例すら出さない Java のマニュアルよりはマシ。