評価戦略について

コーディングを支える技術」の書評で、評価戦略についていくつかご意見をいただきました。

id:dankogai 評価戦略がまるまる抜けてしまったのは残念である。かつては事実上先行評価しかなく、評価戦略において選択肢があること自体ほとんど知られていなかったが、今では遅延評価を主体とする言語も普及したとまでは言えないものの、充分実用的に使われるようになってきている。著者はもちろんHaskellを知っているし、本書にも登場するにも関わらず、これが抜けているというのはなんとももったいない。もし改訂版を著す機会があったら、絶対に一章割いていただきたい。

http://blog.livedoor.jp/dankogai/archives/51864510.html


確かに、世の中の大部分の言語が先行評価なせいで「そういうもんだ」と思い込んでしまっている人も多そうですね。そういう人にHaskellでunsafePerformIOとか使って「ほら違う言語もある」と見せてあげると目からウロコを落とせていいかもしれません。その上でサンクとかの説明に進むか、それとも時代を遡ってALGOL 60の名前渡しとかの話をするか…。

id:matarillo 先行/遅延がないという指摘。ifや?:の話にからめて正格/非正格の話をしてもよかったはず。(ifを関数にできるか?とかそういうヤーツ)

http://b.hatena.ne.jp/entry/blog.livedoor.jp/dankogai/archives/51864510.html


なるほど、Smalltalkが条件分岐を実現している仕組みを解説するとか、LISPのマクロを使って自分でifを作ってみるっていうのも面白いかもしれませんね。


…と普段だとこれくらいで済ませてしまうのですが、これはきっと弾さんのような「わかっている人」を読者として想定しているせいなんですね。このテンションで書籍の原稿を書いてしまうと読者は置いてけぼりになってしまいます。

そこで、本の下書きを書くつもりでブログ記事を書いて公開することにします。


関数fooと関数barが foo(bar(1)) という形で使われていたら、どちらの関数が先に呼ばれるでしょうか?

例えばPythonで下のようなコードを書いて実行したらどうなるでしょう?

def foo(x):
    print "foo"
    return x + 1

def bar(x):
    print "bar"
    return x * 2

print foo(bar(1))

このコードの実行結果は以下のようになります。barが先に呼ばれているわけです。

bar
foo
3

この結果に驚く人はさほど多くないかなと思います。C言語でもJavaでも同じ挙動ですから。

でも、この挙動はすべての言語で共通というわけではありません。Haskellの例を見てみましょう。

import System.IO.Unsafe

foo x = unsafePerformIO $ do
          print "foo"
          return (x + 1)

bar x = unsafePerformIO $ do
          print "bar"
          return (x * 2)

main = print $ foo(bar(1))

このコードの実行結果は以下のようになります。先ほどと違ってfooが先に呼ばれていることがわかりますね。

"foo"
"bar"
3

このように「どこから先に評価するか」(評価戦略)は言語によって異なります。多くの言語は、関数 foo の引数 bar(1) を関数 foo より前に評価します。これを「正格評価」と言います。一方Haskellは正格でない評価(通称「遅延評価」)がデフォルトの挙動です。(脚注:seqや$!などの正格評価をするためだけの関数や演算子が用意されているので、正格評価が必要な時にはこれらを使います。)



続き(予定)

  • 遅延評価のメリット:必要ないものは計算しない
  • 遅延評価の仕組み:「あとでやる」の話
  • 遅延評価のコスト:「あとでやる」を覚えておくために消費されるメモリの話

…これだと名前渡しやSmalltalkの出番がないですねぇ。



追記:今回はunsafePerformIOという裏口を使って無理やりどちらが先に評価されているかを観察してますが、コンパイラにとってはfooから評価してもbarから評価しても「計算結果が3になる」という結論は変わりません。そこでコンパイラが何らかの都合で「barを先に評価したいな」と考え、barを先に評価する可能性は否定できません。「bar(1) を関数 foo より前に評価する」のが正格評価、「fooを先に評価するかもしれないし、barを先に評価するかもしれない」のが正格でない評価、というわけです。

拙著「コーディングを支える技術」の読者から頂いた質問など対して、こんな感じで補足記事を書いて行きたいと思っています。質問・感想はおきがねなくどうぞ。

拙著に関する他のエントリーは「「コーディングを支える技術」著者公式ページ」からたどれるようにします。