モナドの六つの系統[Functor x Functor]

モナドは「アクション」を表す抽象的な構造である。モナドは、Haskellにさまざまな概念に対する記述能力をもたらす。

モナドの基礎

  • return :: a -> m a: 純粋な値をモナドで包む。
  • m >>= f :: m a -> (a -> m b) -> m b: モナドmに包まれた値をfに渡し、その結果として現れたモナドを結合する。
  • 固有アクション: それぞれのモナドに固有の方法でモナドを生み出す。
  • 実行: モナドに包まれた値を、より根源的な形に還元する。

モナド

モナドに以下の三つの制約を課すことによって、最低限度の記述能力を保証している。

return a >>= k == k a
m >>= return == m
m >>= (\x -> k x >>= h) == (m >>= k) >>= h

より強い制約は、より強い力を生み出す。

モナドの分類

モナドは、以下の6つの系統に分類できる。

f:id:fumiexcel:20130605170915p:plain

隣り合っているものほど相性がよく、近い性質を持つことが多い。

内包系

Identity, Maybe, Either, [], Iterateeなど

値を何らかの形で包みつつ、モナドの性質を付与したもの。失敗する可能性のある計算に使用できるものも多い。ストリームを扱うモナドIterateeは、継続系の性質も併せ持つ。

  • Nothing :: Maybe a (固有アクション)計算をすべてに終わりにする。これが出現すると、(>>=)は一切意味をなさなくなる。

出力系

主にWriter

値を出力できるという性質を備えたモナド

  • tell :: Monoid w => w -> Writer w () (固有アクション)モナドに値を送り込む。モナドが結合できるという制約から、送る値も結合可能でなければならない。

  • runWriter :: Writer w a -> (a, w) (実行)計算の結果と、書き込まれた値を取り出す。

環境系

主にReader

モナドの中で値を取り出し、自由に使用できるという性質を持つ。

  • ask :: Reader r r (固有アクション)環境から値を取り出す。

  • runReader :: Reader r a -> r -> a (実行)環境を設定し、その下で行われた計算の結果を求める。環境に持たせる値はどんなものでもよい。

世界系

IO, STM, ST, Q, Freeなど

現実を含む、世界に作用できる特殊なモナドHaskellにおける、副作用を表現する唯一の方法である。

  • putStrLn :: String -> IO () (固有アクション)現実世界の標準出力に文字列を送る。

  • getLine :: IO String (固有アクション)現実世界の標準入力から、一行分の文字列を取り出す。

  • unIO :: IO a -> (State# RealWorld -> (# State# RealWorld, a #)) (実行)GHCのIOの中身は、現実世界を受け取り、変更後の現実世界と結果を返す関数である。通常、我々がこの型 を目にすることはない。

  • liftF :: Functor f => f a -> Free f a (固有アクション)すべてのFunctorには、自由モナド(free monad)と呼ばれるモナドが対応する。自由モナドは強力な抽象化、コードの分離を行うことができる*1

  • singleton :: t a -> Program t a (固有アクション)なんと、この世にはすべてのデータ型にモナドを対応させる仕組みが存在するのだ*2。筆者はこれを最強のモナドの一つだと確信している。

状態系

主にState

モナドの中なら、値を読み込んだり書き込んだりできる。

  • get :: State s s (固有アクション)現在の状態を読み込む。

  • put :: s -> State s () (固有アクション)現在の状態を置き換える。

  • runState :: State s a -> s -> (a, s) (実行)初期状態を与えて、結果と変更後の状態を取得する。

継続系のCodensityモナドを使うと、環境系モナドの性質を状態系モナドに変化させることができる。

継続系

Cont, CC, Parser, Codensity, Logicなど

「継続」と呼ばれる、計算の結果を受け取る関数を扱うモナド。表現力が高く、興味深い性質を示すものも多い。

  • cont :: ((a -> r) -> r) -> Cont r a (固有アクション)継続を受け取る関数からモナドを作る。rの型がわかっているとき、非常に柔軟な処理が可能となる。

  • runCont :: Cont r a -> (a -> r) -> r (実行)継続を渡し、計算の結果を求める。

  • dnew :: CC (Dynvar CC a) (固有アクション)限定継続モナドCCの中で、変更可能な変数*3を具現化する。IOなどの現実世界と関わる処理は一切ない。

ほとんどのモナドはこの継続系の変種として表現できるという面白い特徴を持つ。Logicモナド継続系でありながら非常に内包系に近い特性を持つ*4

まとめ

良いプログラムを書くには、これら六系統のモナドをバランス良く使いこなしていくことが大事である。

参考文献