Haskellに副作用はないのか?

Haskellには副作用はないのか?

議論に微かな違和感があるような気がしたのですが,それが何にたいする違和感かよくわかりませんでした.件の記事を読みかえして,また違和感を感じたので,すこし考えてみることにします.

「副作用」って何のことを言っているの?

この手の議論が判りにくくなるのは主たる原因はキーワードが定義が明示されてないことにあると思っています.ここでの議論のキーワードはもちろん,「Haskell」と「副作用」です.件の記事では「Haskellの範囲」については定義が示されていますが,「副作用」については定義が示されていません.

「副作用」=「入出力」なの?

私の頭のなかでは「副作用」というのは,「変数束縛の変更」です.だから,「プログラミング言語に副作用がある」と「プログラミング言語では変数の束縛を変更がある」とは同じことです.「Haskellには変数束縛の変更」はありませんから,Haskellには「副作用はない」になります.「Haskellの範囲はどこまでか」の議論をする必要がないのです.

つまり,「副作用」の定義が「変数束縛の変更」であるならこれで議論は終りになるはずで,評価とか,実行とか,IOとか,Worldとかの話に行くまえに終っているはずです.

でもそうならない(それで違和感があったのかな)ということは,「副作用」の定義としては別のものを想定しているということです.では「副作用」とは何のことをいっているのでしょう.

getChar :: IO Char の振舞いに焦点をあててみましょう.

たとえば、getChar :: IO Char は、実行されるごとに(同じこともあるが)別の文字を返す。

副作用とは,このように同じ変数は同じ値を表わすという性質(参照透明性)を破壊していることと定義していると思われます.getCharは入出力のプリミティブと考えてよいので,「getCharの存在」と「入出力の存在」とは同じことと考えてもよいでしょう.そうすると,「Haskellに副作用がある」という主張は「Haskellには入出力(getChar)がある.入出力(getChar)には副作用がある.だからHaskellには副作用がある」という主張とみなしてよいと思われます.

「どこまでがHaskellか」なの?

DanoMoiとHaskellでは,getCharは評価してもいつも同じ命令書になるだけなので,参照透明性は破壊さていない.と主張しています.

Real World Haskellでは『IO をつかって,「副作用のあるコード」を孤立分離する...』などと書かれている.彼らがHaskellはrun-timeも含んでHaskellであるという立場を取っているかどうか,直截にその記述はないので断言はできませんが,彼らは,「実行されれば副作用があるように見えるので,それをあるものとして考えるということをやっている」わけです.これはまさしく,「遠心力」とか「慣性力」という「見かけの力」をあるものとして説明できるというのと同じです.「見かけ」のものであることを説明せずに,「副作用」といっているので混乱するわけですよね.

IOモナドはWorldという状態を考えれば,状態モナドとして表現できる.IOの実行は実引数としてWorldを渡すことだと説明できます.Worldが環境です.この説明はIOの「実行」や「副作用」に見えるものはHaskellの世界ではどういうふうに記述できるかということを説明できるかを示しています.しかし,Worldとか状態モナドは純粋な記述でIOの機構を説明するモデルの1つにすぎません.実際GHCではこのモデルを採用していますが,それは処理系の実装上都合にすぎないということに注意する必要があります.

いずれの立場にせよ.説明者たちは「Haskellの範囲」ということを意識しているわけではないように思います.「だから判りにくいのだ」ということになるわけです.ただ,私の違和感は,「どのような立場で副作用の有無をいっているのかの説明が足りないことは確かだけれど,その足りない説明というのは「Haskellの範囲」のことか」ということかもしれません.

「getChar は(引数がないのに)実行されるごとに別の文字を返す」の?

だんだん違和感の所在が判ってきたような気がします.ここですたぶん.

getCharは確かに「標準入力から文字を読み込め」という命令書だけど,実行されてもgetCharが読み込んだ文字の値になるわけじゃないです.getCharがその文字のを返すわけじゃないです.

もちろん,getCharが実行されるとレジスタとかメモリの内容が書き変ったりするでしょうからそこを見ているグローバル変数があれば,副作用がおこっていると言えます.その層にまでおりて議論するなら,「実行器を含んで副作用のない言語はないゆえに,Haskellにも当然副作用はある」という主張はただしいと思います.しかし,そのようなグローバル変数Haskellで書いたコードそのものには現れません.Haskellで書いたコードに現れないことに対して云々してはたして意味があるかという疑問があるわけです.

たぶん違和感のありかは,「実行器まで含めれば副作用があるということが可能である」,という主張なんだと思います.「実行器までがHaskell」というとき何を意味しているのがが曖昧なのです.

みかけの副作用

では副作用があるという主張が説明できないのかというと,そうではなくて「副作用」というものを考えたほうが説明しやすいという場面は想定できると思います.「慣性力」という「みかけの力」を考えたほうが説明しやすい場面を想定できるのと同じです.命令型言語プログラマにとって,入出力は副作用です.Haskellの入出力に関しても副作用があるに違いないと考えるのは自然のなりゆきです.そこで「みかけの副作用」というのを考えるというのはありでしょう.あくまで勝手な想像ですが,Real World Haskellの著者たちはそのような読者を想定しているのだと思います.でもそれが「見かけ」であることを説明していませんから混乱を招いていると思います.

何が違和感の原因だったか

「説明なしに副作用があるといったり,ないといったりするから混乱する」という点には同意します.まったく同感です.

では何が違和感の原因だったかというと,追加すべき説明において

  • 「副作用」が定義されていない
  • Haskellのコードに現れない部分がHaskellの一部とされている説明がある.

という点です.

(更新したら kazu-yamamotoさんがくれたコメントが見えなくなった。orz)
http://d.hatena.ne.jp/nobsun/20090702/1246522405