「モナドのすべて」の読書記録を読む

はてブの注目エントリにあった

は、

の読書記録。

モナドのすべてはHaskellを始めたころに読んで断念していたので改めてよんで見た。
モナドを理解したうえで読むと、書いてあることもたいしたことではない、とはわかる。だが、よくわかってなかった当時これを理解するのはやはり難しかったし、今読んでもやはり表現が難しいなあと思うのである。なんども触れてるけどやっぱり説明する順番はどうもしっくりこないと思う。

とりあえず、自分のモナドの理解としては

  • モナドとは
    • do記法のためにあつらえられたインタフェース(returnと>>=(bind))を持つ構造
    • do記法が使えるためには、型制約以上に追加規則3つが成立している必要がある
  • 二タイプのモナド
    • モナドとして使うために定義された一般的なモナド
      • モナド型とは: コマンド実行をあらわす特定の関数を持つレコード型
      • その関数を実行することで実行結果を得ることができる
      • do記法: モナドオブジェクトを組み合わせた新たなモナドオブジェクト(Commandオブジェクトを組み合わせたCommandオブジェクト)を作る行為
      • return o: コマンド実行関数を実行したとき、oを返すコマンドを作るコンストラク
      • c >>= (\o ->...): cを実行した結果をoとして受け取って、...を処理するコマンドを作るコンストラク
    • リストやMaybeなどのモナド
      • リストやMaybeでモナド風の記述(doのためのモナド構築関数)を使えるようにしたミックスイン
      • あえて使う必然性はあまりない
  • 標準モナド
    • リスト、Maybe、Identity: 先に普通の使い方に慣れて、一般的なモナドを理解した後で使えるように
    • IOモナド: とりあえずmain、putStrLnなどをdo記法で使えればいい
    • Stateモナド
      • コマンド実行: runState Stateモナド 初期状態 -> (結果, 最終状態)
      • 初期状態を与えて、計算(Stateモナドによるコマンドの)実行結果と、その実行によって変化した最終状態を返すコマンド
      • 最終状態を次のrunStateの初期状態として使うことで、状態を更新されるデータとしてを扱うことができる
      • get: 状態を取り出すStateモナド(コマンド)
      • put: 状態を更新するStateモナド(コマンド)

Stateモナドでの状態のように、コマンドを実行するとき、すべてのコマンドで参照するわけではないけど、何らかのタイプの情報を必ず通して渡したい場合がある。普通の関数の場合、必ず引数に記述しなくてはいけないが、>>=やdoで構築するモナド関数では、こういった情報受け渡しが(runStateの側へ)隠蔽される。getやputのような特殊なrunStateを作るモナド関数を作ることで、逆にこういう情報にアクセスさせることができるようになる(たとえば、>>=で保持するカウンターを増加させるようなモナドも作れるわけだが、そのアクセスできるためのモナド関数を作らない限り、doや>>=だけではアクセスできないことになる)。

    • Readerモナド
      • コマンド実行: runReader Readerモナド 初期状態 -> 結果
      • 終結果は無条件に返さないStateの亜種
      • ask: 状態を取り出すReaderモナド
      • local f c: 環境を関数fでアップデートして、その環境上でReaderモナドcを実行するReaderモナド
    • Writerモナド
      • コマンド実行: runWriter Writerモナド -> (結果, 最終状態)
      • 初期状態がないStateの亜種
      • 初期状態がない代わりに状態はMonoid(「空」を意味するmemptyと、「追加」を意味するmappendが定義されている型)である必要がある
      • tell l: 状態にlを追加する
      • listen w: Writerモナドから結果と値を取り出すWriterモナド
    • Contモナド
      • コマンド実行: runCont Contモナド 継続関数 -> 結果
      • 大域脱出を行うcontinuationは関数を継続渡しスタイル(CPS)にすれば実現できる。関数をいちいち継続渡しスタイルで書くではなく、戻り値をContモナド形式で返す関数にすることで、継続渡しで渡す関数の引数を見えなくし、普通の関数のように戻り値で結果を書けるようにしている
      • 継続関数kは何かを受け取って最終結果を返す関数、普通はid
      • callCC (\k -> ...): k aを実行すると、その結果が(runCont ...) ...の実行結果になる

参考: 継続渡しスタイル: http://en.wikipedia.org/wiki/Continuation_passing_style

  • モナド変換子
    • あるモナド型の処理の間に、別のモナド型の処理をはさめるようにする方法
    • 例: State処理の中でIO処理をしたい: StateT MyState (IO a)
      • runStateT StateTモナド 初期状態: IO (結果, 最終状態)
      • lift $ IO処理: IO処理の結果を返すStateモナドにさせる
    • liftIO: IO処理専用のlift
  • 物理的アナロジー
    • ローダー(return s)とワーカー(\x -> ...)をコンベヤー(>>=)でつなぐ
    • さらにワーカーをコンベヤーでつなぎ続けられる

物理アナロジーだとワーカーの非対称性が気持ち悪いかも。この理解方法の場合、arrowsのほうが回路図的な入出力があるものになっているのでわかりやすいかも。