最終的な結果だけでなく、中間結果も出力したい
例えば、次のような簡単な計算を行わせた場合、
(1 + 2) * 3 - 4
答えは `5’ となる。
関数 (演算) が適用されるそれぞれの過程の結果を考えると、
1 + 2 => 3
3 * 3 => 9
9 – 4 => 5
となる。このように最終的な結果だけではなく、計算途中の結果も表示したい場合にはどうすればいいのだろう?
let 式で中間結果を束縛
まず考えられる方法は、それぞれの中間結果を後で参照できるように let 式内で変数に束縛。結果をコンマ区切りで表示するなら、
main = do let a = 1 + 2
b = a * 3
c = b - 4
print $ show a ++ "," ++ show b ++ "," ++ show c ++ ","
結果は、
"3,9,5,"
結果の型を (値, 出力する文字列) に変更
次に計算の方法を少し変えてみる。先ほどは計算の過程の結果を文字列で返したが、今度は
- 計算の結果
- 出力する文字列
の 2 つの部分に分ける。つまり、各計算の結果が以下のようにタプルで返されるように変更。
計算 => (値, 出力する文字列)
main = let (a, log1) = (x, show x) where x = (1 + 2)
(b, log2) = (y, show y) where y = (a * 3)
(c, log3) = (z, show z) where z = (b - 4)
in print (c, log1 ++ "," ++ log2 ++ "," ++ log3 ++ ",")
これを実行すると、
(5,"3,9,5,")
これでは長くてみっともないので、出力関数 out を定義。
out x = (x, show x)
これを使うと、
main = let (a, log1) = out (1 + 2)
(b, log2) = out (a * 3)
(c, log3) = out (b - 4)
in print (c, log1 ++ "," ++ log2 ++ "," ++ log3 ++ ",")
式の参照関係に表われるパターン
ここで let 式内の計算の参照関係について注目すると、下図のように「上の計算の結果」を「下の計算」が順々に参照していることがわかる。( let 式なので式の並び順は本質的に関係ない。)
これは State モナドについて考えたとき と同じパターン。全体の計算の連なりの中において、2 つの式の参照関係のみに注目。2 つの間の連なり方を考えると、式の「つなげ方」を共通の部品として取り出すことが可能。その「つなぎ」を利用して全体の連なりを再構成できる。
2 つの式の つなぎ方 を定義
では、2 つの式をつなげる関数 comb について考える。 ( Monad の >>= に相当。 Philip Wadler の Monads for functional programming の “2.9 Variation three, revisited: Output” を真似した。)
comb 関数に渡す引数を m, n とし、n が m の結果を参照すると考える。先ほどの例で言うと let 式において out 関数の結果を束縛する部分。
comb m n = let (a, log1) = m
計算 m の結果をタプル (a, log1) に束縛。 a が「計算の結果」で、log1 は「出力する文字列」に相当。
次に、上記の計算の結果 a を参照する計算 n の結果をタプル (b, log2) に束縛。
comb m n = let (a, log1) = m
(b, log2) = n a
b は n a の「計算の結果」で、log2 は「出力する文字列」に相当。
最後に、先ほどの計算の結果 b と、計算 m による出力と (log1) と 計算 n による出力 (log2) を結合したものをタプルにつめて返す。
comb m n = let (a, log1) = m
(b, log2) = n a
in (b, log1 ++ log2)
下図の具体例では、つなぎ を考えずに 3 つの計算を一気に行っているので つなぎ の定義と変数名が異なるが、最終的な結果 c と中間結果として出力したものを結合し、タプルに入れて返すことに相当する。
つなぎ を使ってみる
上記の comb 関数を使い、先ほどの計算を再定義する。
comb m n = let (a, log1) = m
(b, log2) = n a
in (b, log1 ++ log2)
out x = (x, show x ++ ",")
main = print $ out (1 + 2) `comb` \a ->
out (a * 3) `comb` \b ->
out (b - 4)
( out 関数においてコンマを出力するように変更した。)
comb 関数を使うことにより、各々の計算の参照関係に気を使う必要がなくなった。どの式がどの結果に依存するかを明示しなくても、 comb 関数のつなぎ方によりその関係が成立する。
(上記 main 関数の定義において、括弧は省略しているが、無名関数は「できるだけ右へ拡張される」ように解析されることに注意。)
前の結果に関心のない つなぎ
ここで、一連の本質的な計算とは関わりのない、文字列を出力をするための関数 outStr を定義してみる。
outStr x = ((), x)
計算の内容に関わらないので、返されるタプルの第1要素は () 。これを使い次の計算を実行すると、
main = print $ outStr "begin: " `comb` \_ ->
out (1 + 2) `comb` \a ->
out (a * 3) `comb` \b ->
out (b - 4) `comb` \_->
outStr " :end"
結果は、
((),"begin: 3,9,5, :end")
あらら… (@_@; 、肝心のタプルの第1要素が計算の結果でなく () になってしまった。 これは後で修正するとして後回し。先に comb 関数を使い、outStr 関数のような本質的な計算に関わらない関数をつなげるための関数を定義しおく。 (Monad の >> に相当)
comb_ m n = m `comb` \x -> n
これで上記を次のように書き換えることができる。
main = print $ outStr "begin: " `comb_`
out (1 + 2) `comb` \a ->
out (a * 3) `comb` \b ->
out (b - 4) `comb_`
outStr " :end"
計算の対象 と 出力する内容を分離する
先ほどの問題、最後で返される値のタプルの第1要素が () となってしまうことに対して、次のように書いたとする。
main = print $ outStr "begin: " `comb_`
out (1 + 2) `comb` \a ->
out (a * 3) `comb` \b ->
out (b - 4) `comb` \c ->
outStr " :end" `comb_`
out c
実行すると結果は、
(5,"begin: 3,9,5, :end5,")
結果は返ってきたけれど、最後に余分なものが出力されてしまった。 (+_+)
これは out 関数において、次の二つがごちゃ混ぜになっていることが原因。
- 本質的な計算の結果を返す
- 出力する内容を返す
また、上記の問題に加えて、本質的な計算の過程の一部を出力したいくないということにも対応できない。例えば、最初の 1+2 の結果を出力せずに計算を進めることは、このままではできない。 out 関数を使って計算を進め、同時に結果を出力しているからだ。
これを解決するために out 関数を変更する。これまでは出力する内容と伴に結果を返していたけれど、出力のみに特化。
out x = ((), show x ++ ",")
次に、出力は空だけれど、結果を返すことに特化した関数を定義。 (Monad の return に相当)
ret x = (x, "")
この二つを使って先ほどの関数を定義しなおすと、
main = print $ outStr "begin: " `comb_`
ret (1 + 2) `comb` \a ->
out a `comb_`
ret (a * 3) `comb` \b ->
out b `comb_`
ret (b - 4) `comb` \c ->
out c `comb_`
outStr " :end" `comb_`
ret c
全体のソースコードはこちら。
命令型言語との比較
上記の定義は `comb` に目をつむれば、以下のように Python で定義したときとよく似ている。
def main():
a = 1 + 2
print a
b = a * 3
print b
c = b - 4
print c
return c
print main()
これを見ると、Python で「前に定義した変数を参照する」ことは、Haskell において「内側の関数 (無名関数) の中から外側の引数を参照する」ことに相当することがわかる。
参考文献
- Monads for functional programming
- 2.4 Variation three: Output
- 2.9 Variation three, revisited: Output
![img01-31-2010[1].png](http://static.flickr.com/4004/4317504854_81ed1befb9.jpg)
![img01-31-2010[2].png](http://static.flickr.com/2746/4317589398_d6df94e3e5.jpg)
![img01-31-2010[4].png](http://static.flickr.com/4027/4317606712_63fd2bf7dc.jpg)
![img01-31-2010[5].png](http://static.flickr.com/4051/4316893657_ee178644a8.jpg)
![img01-21-2010[7].png](http://static.flickr.com/4007/4291995368_a5bca3e8a7.jpg)
![img01-21-2010[8].png](http://static.flickr.com/2687/4291995176_532dd5d475.jpg)
![img01-21-2010[7] - コピー.png](http://static.flickr.com/2730/4291254219_f5fcde1458.jpg)
![img01-21-2010[9].png](http://static.flickr.com/4008/4292007952_7b33f9d8e2.jpg)

![img01-22-2010[2].png](http://static.flickr.com/4022/4294728825_69145d86fd.jpg)

![img01-13-2010[1].png](http://static.flickr.com/2799/4271232772_2a31bb1786.jpg)
![CropperCapture2010[1].png](http://static.flickr.com/4015/4264675997_411a6a5410.jpg)
![CropperCapture2010[2].png](http://static.flickr.com/4027/4265441520_5dc911a28f.jpg)




