IOモナドのまとめ

IOモナド関連の記事が長くなりすぎたので、ここらでまとめてみたい。IOモナドが何であるかということを数学的に厳密に知る必要はないが、大まかな意味を知っていると便利なのでまとめてみる。ただし、管理人も数学的に厳密に理解したわけではないので間違いも含まれているかもしれない。

最初に知っていると便利なのは圏とは何かということだ。数学的な厳密さを無視して言うと、圏とは、データの集まりとそれを加工する関数の集まりをひとまとめにして指す言葉だ。たとえば、Haskell のプログラムの世界は圏だ。Haskellのプログラムは、様々な型のデータとそれを加工して別のデータにする関数からなっているからだ。

こういう圏には、関数と関数の合成の操作がなければならない。Haskell の世界ではそれは関数と関数の合成関数をつくる操作だ。また、データの集合の要素を同じ要素に対応付ける単位元という関数が必要だ。Haskell では、id x = x のようにそのような関数は簡単に定義できる。

圏とは、このような、データと関数の集まりがあり、それらに関数の合成と、単位元と呼ばれる関数が定義されているシステムを指す言葉だ。くりかえすが、Haskell のプログラムの世界はこのような圏なのだ。

こういう圏とよばれるシステムにはいろいろなものがあり、それらは似通った構造を持っているため、関手という関数でそれらの圏の間を関連付ける事ができる。圏Cと圏Dという二つの圏があり、そのデータをX、Y、関数の集まりを {f}, {g} とすると、関手Fは Y = F(X), {g} = F ({f}) のようにデータの集まりと、関数の集まりをそれぞれ圏Cから圏Dへ写像することができる。

関手はひとつの圏の構造を他の圏に映しだすプロジェクターの役割を果たしていると考えて良い。

このような関手は、圏と他の圏の間だけでなく、自分自身の圏にも張る事ができる。これを自己関手という。自己関手Fがあると、圏Cは自分自身の中の圏C’ に自分自身の影像を投射できる。合わせ鏡や、ディスプレイにビデオカメラを向けた時の影像のようなものだ。関手によって圏のなかに作られた、部分的な圏がIOモナドの世界だ。

IOモナドについて調べるとき、IOモナドという言葉が何を指しているのかが混乱することがある。IOモナドの世界を構成するIO型のデータなのか、それともIOモナドの世界の関数なのか判然としない。実は、IOモナドとは、Haskellの圏をIOモナドの圏に映しだす、コンストラクター IO で表されるHaskellの自己関手と、>>= で表される関数の合成規則と、return 関数という単位元の関数の三組の呼称なのだ。この3組の規則で、IOモナドの Kleisli 圏という圏が作られるが、その圏の要素が、IO型のデータであり、「引数ひとつ、戻値IO型」の関数だ。

このようなIOモナドの圏は、Haskell の通常の圏から隔離されており、Haskell のデータや関数を、IOモナドの Kleisli 圏のメンバーに変換することはできるが、その逆はできない。このため、IOモナドの世界で発生した副作用は、通常のHaskell の世界には影響しないのだ。

概念的には、Haskell の圏がIOモナドの圏に写像されて、データや関数をIOモナドの関数やデータに出来るということがわかったが、実際にはどうすればいいのだろうか。

それは、簡単にいうと、Haskellのデータは、return "hello" のように、return 関数の引数にしてやるとIOモナドの圏のデータになるし、 f x = return ( x + 1 ) のように、「引数一つで戻値がIO型」の関数を定義してやると、それがIOモナドの圏の関数になるということだ。IOモナドの世界のメンバーをつくるのはたったこれだけの規則で十分なのだ。

IOモナドの圏のデータや関数を作る方法はわかったが、それらを使ってどうしてプログラムを組み立てるのだろうかという疑問は当然湧いてくる。しかし、IOモナドの圏の世界にはデータと関数と、あとは関数同士の演算規則しかない。したがって、IOモナドの圏のなかでプログラムを作るというのは、IOモナドの関数を >>= 演算子で合成するという方法しかない。したがって、IOモナドの世界のプログラムは、function1 >>= function2 >>= function3 のようにパイプを繋ぐように関数をつなげることなのだ。do 記法はこのパイプラインを手続き型のプログラムに見せるためのシンタックスシュガーでしかない。

具体的なIOモナドのプログラミングについての検討はこれまでのエントリーでやってきたので、ここでは、その要点を箇条書きにしてみよう。また、話を簡単にするために、IOモナドの圏のことを便宜的にIOモナドと呼ぶことにする。

  1. IOモナドはデータと関数からなる圏である。
  2. HaskellのデータのIOモナドへのデータの変換は、return 関数を使う。
  3. IOモナドの関数は「引数がひとつで戻値がIO型」という関数を定義することで作ることができる。
  4. IOモナドのプログラムは、IOモナドのデータやIOモナドの関数を >>= (bind演算子)でパイプラインのように結合することで実現できる。
  5. パイプラインの中を流れるデータは function1 >>= function2 のような場合、function1 の戻値の IO a のうちパラメータの a だけが、function2 の引数として渡される。
  6. do 記法は >>= 演算子でつながれているプログラムを手続き型のように見せるためのシンタックスシュガーである。


実は、上のプログラミングの方法は圏の世界のプログラムに共通するやりかただ。>>=演算子を関数の合成 . 演算子に置き換えるとIOモナドのプログラムの方法はそのままHaskell のプログラムの方法と重なってくる。IOモナドのプログラミングが難しいのではなくて、手続き型のイメージではなく、関数型のプログラミングのイメージに慣れることが難しかったのだ。

Haskellはなんといっても記述の簡潔さや、高階関数を使ったプログラムのモジュール化に大きな魅力がある。しかし、それを利用するためには、今までの手続き型の言語の発想を一旦捨てて、関数型プログラミングの発想を身につける必要がある。

おまけ

今日のHaskell (Haskellに慣れるために毎回小さなHaskellプログラムをひとつだけ作ってみることにする。)

Hugs> getLine >>= return . (read :: String -> [Int]) >>= return . sum >>= print
[1,2,3,4,5]
15

コンソールから、整数のリストを表す文字列を読み取って、整数のリストに変換し、その要素の総和をとるプログラム。
[PR]
by tnomura9 | 2010-11-01 03:53 | Haskell | Comments(0)
<< IOモナドと末尾再帰の相性がいい 再帰関数によるループを Ruby で >>