10.3 プログラムの合成
前々回の記事で紹介した二つのプログラムを決後するフィッシュ・オペレータをHaskellで実現することを考えよう。二つのプログラムを合成するとは、下図を実現することである。
上図では二つのプログラム\(f,g\)がある。それぞれのプログラムは\(a,b\)を入力する。これらの値はコンピュータの世界で通用するものだ。そして、これらのプログラムは\(m \ b, m \ c\)を出力する。これらの値は人間の世界で通用するものだ。最初のプログラム\(f\)が吐き出す出力\(m \ b\)は、次のプログラム\(g\)への入力\(b\)となっているが、出力を直接入力として渡すわけにはいかない。人間の世界からコンピュータの世界への変換を行わないといけない。さて、これをHaskellで実現することを考える。
このように言われても戸惑うだろう。そこで、入出力が直接つながっている関数の合成\((.)\)をHaskellで実現し、そこからどのように実現したらよいかを学び取ることにしよう。
関数の合成は二つの関数を得て、それを合成し一つの関数として返すことである。Hasekllで実現すると次のようになる。最初の行が型シグネチャである。
(.) :: (b -> c) -> (a -> b) -> (a -> c) g . f = \a -> let b = f a in g b
上記のプログラムで、\((b->c),(a->b)\)が入力される関数\(g,f\)である。\((a->c)\)が出力される関数で\(g \circ f\)である。
二番目の行が関数\((.)\)の定義である。\(a\)を入力し、それを関数\(f\)に適用し、その出力が\(b\)である。そして、この値を関数\(f\)に適用し、その出力が\(C\)である。
これをProgramというモジュールの一部とする。モジュールは次のようになる。
module Program ((Program..))) where (.) :: (b -> c) -> (a -> b) -> (a -> c) g . f = \a -> let b = f a in g b
これを用いてみよう。合成関数\((.)\)はHaskellでは最初から定義され与えられているので、元々あったものと区別するために、\((P..)\)で呼ぶようにし、次のようにメインモジュールを作成する。
import Program as P f a = a + 2 g a = a * 3 h = g P.. f
上のプログラムで\(h\)が合成された関数である。これを用いてみよう。
Prelude> :load "Main.hs" [1 of 2] Compiling Program ( Program.hs, interpreted ) [2 of 2] Compiling Main ( Main.hs, interpreted ) Ok, modules loaded: Main, Program. *Main> h 5 21 *Main> h (-3) -3
期待する結果は得られているようである。それでは、関数の合成のコードを見ながらプログラムの合成についてのコードを作成することにしよう。
変えなければならないところは、最後の行の\( g b\)である。ここは、出力\(b\)を得て、\(g\)に適応している。そこで同じようにしよう。最初のプログラムの出力は\(mb\)としよう。これは型\(m \ b\)に属す値という意味でこのようにした。また、これを受けて\(g\)に適応するのは\(mb >>= g\)としよう。
即ち、次のようにする。
(>=>) :: ( a -> m b) -> ( b -> m c) -> (a -> m c) f >=> g = \a -> let mb = f a in mb >>= g
関数合成のコードは次のようになっていた。これと比較すると対応関係がよく分かる。
(.) :: (b -> c) -> (a -> b) -> (a -> c) g . f = \a -> let b = f a in g b
前々回の記事で紹介したが、クライスリ圏は、\(>=>\)と\(return\)を定義すれば構成できる。\(>=>\)はいま出てきた\(>>=\)から得られるので、クライスリ圏は、\(>>=\)と\(return\)からも構成できる。そこで、この二つの関数を備えているクラスをモナドと呼ぶことにしよう。このように定めるとモナドは次のようになる。
class Monad m where (>>=) :: m a -> (a -> m b) -> m b return :: a -> m a
上記で、\(>>=\)は人間の世界からの入力\(m \ a\)、人間の世界への出力\(m \ b\)としている。しかし、ここで用いられる関数\(a \rightarrow m \ b\)は\(m \ a\)をコンピュータの世界の値\(a\)に変換されたものを用いて計算している。
今、人間の世界に移す時は[ ]で値を囲むことにする。そこで、[ ] をモナドとすることを考えよう。これは、次のように定義できる。
class Monad m where instance Monad [] where [a] >>= g = g a return a = [a]
そこで、今までのものをProgramというモジュールにまとめる。この時、Haskellで使われている関数との衝突を避けるために、これらには\(Program.\)をつける。
module Program (Program.Monad, (>=>)) where (>=>) :: (Program.Monad m) => ( a -> m b) -> ( b -> m c) -> (a -> m c) f >=> g = \a -> let mb = f a in mb Program.>>= g class Monad m where (>>=) :: m a -> (a -> m b) -> m b return :: a -> m a instance Program.Monad [] where [a] >>= g = g a return a = [a]
それではこのプログラムを使ってみよう。利用するプログラムは以下のとおりである。
import Program as P f' a = [a + 2] g' a = [a * 3] h' = f' >=> g'
先ほどと変わらないが、出力がカッコで囲まれていることに注意してほしい。
Prelude> :load "Main.hs" [1 of 2] Compiling Program ( Program.hs, interpreted ) [2 of 2] Compiling Main ( Main.hs, interpreted ) Ok, modules loaded: Main, Program. *Main> h' 5 [21] *Main> h' (-3) [-3]
これで、プログラムの合成ができるようになった。次回は、集合と冪集合での関係を用いてモナドを定義する。