ある人は、Haskell には副作用がないと言う。また、別のある人は Haskell には副作用があると言う。Haskell を学ぶ者にとって、こういった意見の食い違いが、Haskell を得体の知れない言語にし、学習の障壁となっているかもしれない。そこで、この記事では、なぜこのような意見の相違が生まれるのかについて説明したいと思う。
向心力か遠心力か?
僕は高校三年になって受験勉強をするまで、物理の運動方程式が得体の知れないものに思えていた。
例として円運動を考えよう。ある説明では、円運動をしている物体には向心力が働いていると説明されている。また別の説明では、遠心力が働くと説明されている。一体、どういうことだろう?
受験勉強でたくさんの問題を解いて、ようやく分かった。これらの説明はどちらも正しい。すなわち、観測者がどこにいるかによって、説明の仕方が異なるのだと。
観測者が円運動をする物体の外にいる場合、この物体は「運動」している。だから、運動方程式 F=ma という等式を用いて、問題を解く。
一方、観測者が円運動をする物体の中にいる場合、この物体は「静止」している。だから、見かけの力である遠心力が働き、向心力の大きさと遠心力の大きさは釣り合っているという等式を書いて、問題を解く。
どちらの方法で解いても、同じ答えが出てくる。なぜなら、同じ事象を立場によって異なった見解で解いているだけだからだ。
高校三年生の夏に、このことに気付いたとき、僕は物理という学問に光が射した感じがしたのをよく覚えている。分かってしまえば簡単なことだ。しかも説明するのも簡単だ。
ただ、僕が出会った教科書や参考書には、こんな簡単なことさえ書かれていなかった。それが物理に対する障壁となっていた。
僕がもし、高校生に対して物理を教える機会を得るとしたら、たぶんこの一点だけを教えるだろう。これさえ理解してしまえば、物理に自信が持てる生徒が多いのではないかと信じるからだ。
Haskell の IO
Haskell の IO では、評価と実行を分離して考える。例として、以下の関数を考えよう。
putStr :: String -> IO ()
putStr は、たとえば putStr "Hello World" のように、String を引数に渡して評価すると、IO () という型の何かを返す。
このように IO a という型、念のため言うが -> などを含まない単体の IO a という型を持つものを Hakell ではアクションと呼ぶ。僕には、カタカナを使って説明した気になっている人は説明が下手だという信念があるので、日本語に直そう。単体の IO a という型を持つものとは、命令書である。
つまり、こういうことだ。putStr "Hello World" は、「"Hello World" を標準出力に出力しろ」という命令書を作る。これが実行されてはじめて、"Hello World" が出力される。
この点が理解できれば、Hakell には 3 つの構成要素があると分かるだろう。
評価器までがHaskellだ
この立場の説明では、Haskell には副作用はない。なぜなら、Haskell が作るのは命令書のみで、それが実行されるのは Haskell の外での話だからだ。
たとえば、getChar :: IO Char は、実行されるごとに(同じこともあるが)別の文字を返す。それでも Haskell には副作用はない。Haskell が作り出すのは、「標準入力から文字を読み込め」という命令書だけであって、実行はしないからだ。
「DanoMoi と Haskell」の説明は、この立場を取っている。
実行器までがHaskellだ
この立場の説明では、Haskell には副作用がある。getChar は(引数がないのに)実行されるごとに別の文字を返すからだ。Haskell では副作用のあるコードには、IO という印が付き、純粋な関数とはっきりと区別できる。
Real World Haskell の説明は、この立場を取っている。