Haskellと副作用の議論の続き
twitter で続いている Haskell と副作用、および参照透明性の議論ですが、twitter ではコードを示しにくいので、ブログに書きます。これに対する返答は、twitter でも構いませんし、ブログに対するコメントでもいいですし、トラックバックでも OK です。
多くの人が納得できる説明が見つかるといいですね。:-)
副作用と参照透明性
僕はもともと命令型言語のプログラマーなので、「副作用」という言葉は命令型言語のプログラマーが使う意味で使っています。
Simon Peyton Jones さんは、Beautifull Code という本の中で「副作用(side effect)とは、変更可能な状態を読んだり書いたりするような何か」と書いています。そして、副作用を表す型を IO a としています。
Brent Yorgey さんは、The Typeclassopediaで「IOモナドだけが魔術的なモナドであるということは、強調する価値があります」と書いています。
僕は、IO に加えて ST も含めると、両者に賛成です。Haskell 98 には、ST は含まれていませんので、彼らの説明には「Haskell 98 の中では」という限定があるのかもしれません。
僕なりに表現すると、ある関数が引数からのみ決まる値を返し、それ以外の仕事をしないときに「副作用がない」と言います。逆に、同じ引数を与えても、異なる値を返すことがあったり、それ以外の仕事をする場合には「副作用がある」と言います。
たとえば、返り値が大域変数の影響を受けていたり、printf のように出力したりする場合は、副作用があるといいます。
「副作用がない」ことと「参照透明である」ことは、同じことだと思っていましたが、どうも違うようです。まだ、これについては、よく分かっていません。
ST で議論してみる
twitter で @ainsophyao さんがこう言っていました。
副作用については、a=f(x);b=f(x);g(a); というプログラムと a=f(x);b=f(x);g(b); というプログラムが異なるものになりうるか、を定義とすればいいと思う。
私もこの定義はよいと思います。
そして、Haskell では異なる値になることを ST を使って示します。本当は IO を使いたかったのですが、Haskell 自体では runIO main と書けないので、議論の対象に実行器を含んでいるのか曖昧になり議論が噛み合ないことが多かったのでやめておきます。
念のため書きますが、僕は実行器を含めて議論しています。実行しなくていいのなら、たとえば C の printf("Hello\n") だって、出力はしないので、副作用はないですからね。
以下が、僕が書いた ST のコードです。
module A where import Data.STRef import Control.Monad.ST foo :: Int foo = runST (do x <- newSTRef 1; a <- f x b <- f x g a) bar :: Int bar = runST (do x <- newSTRef 1; a <- f x b <- f x g b) f x = do modifySTRef x (+1) readSTRef x g x = return x
foo を評価すれば 2、bar を評価すれば 3 が返ってきます。
このコードに、僕が言う意味での副作用がないと思う方は、その理由を教えて下さい。
参照透明であるか否かも、説明していただけると嬉しいです。