ここでは、haskellの動作ではなくて、プログラムの一般的なことを説明するよ。
リンクを貼っておいて、こう書くのも難だけど、ここを読むと理解が混乱するかも知れない。
一応、言語の根幹に関わることだから説明するけど、深く考えない方が良い気がするよ。
副作用(side effect)というのは、メッセージの表示や、変数への代入といった、状態が変化する作用のことを言うんだ。
そうでない作用をする関数を純関数というけど、用語の対応は良く分からない。
ともあれ、副作用は関数型言語を使っていると良く問題にされるんだ。
関数型言語ではない言語は、大抵副作用を持っている命令で構成されている。
変数に計算結果を代入して、それを表示する……のようにね。
でも、関数型言語はそうじゃない。
プログラムの大半は値の宣言で出来ている。
とりあえず、説明のためにCの関数(と呼ばれているもの)とhaskellの関数(と呼ばれているもの)を示そう。
//Cの関数
int fc(int x){return (x*x);}
--haskellの関数
fh x = x*x
このfcとfhは、それぞれの言語でほぼ同じように扱われる。けど、その意味合いは少し違う。
fcはxを与えたらx*xを返せ、と言う命令だ。この命令自体はintと同様に扱えることを明示している。
一方、fh xは値である。この宣言は、実はfh xに対するものなのだ。そう読むと、本当にそのままを表していることが分かる。
ちなみに、fhという関数そのものを宣言したいのであれば、まだ出てきてない表現だけど、
fh = \x -> x
と書くことになる。実質的には上の定義と同じになる。
要は、上の定義だと、fh xがx*xという値として宣言され、そこから推論された結果、fhはxを引数にx*xを返す関数だとされるのである。
で、型推論の結果、xに入る値は*が使える値、つまり数値に限定される。
見た目は似ているけど、意味は大分違う。もちろん、使われ方も違う。
Cの命令は、それ自体が副作用である。returnは、命令を呼び出した先に値を返すという動作を示す。
こういう書き方をしている都合上、他に副作用があるか、命令を使う側からすると判断が付かない。
一方、haskellの定義は単に、値に名前を付けているだけである。値だから、型さえ合っていれば好きに使って良いことが分かる。
値なんだから、副作用なんかあるわけがない。
……そう、haskellは値の定義しかできないのだから、副作用なんか持たないのだ。
この重大な特長のために、純関数型言語と呼ばれている。
それは理解できたけど、なんか怪しい。
プログラミング言語なのだから、変数を使ったり動作をさせたり出来なくちゃおかしい。
実際に触って確かめたように、画面の表示も出来るわけだし(変数はまだ見てないけど)。
では、謎を解明するために、明らかに副作用を持つ、画面を表示するプログラムで考えてみよう。
//C:簡単のため、宣言は省略
int main(void){printf("Hello,world!");return 0;}
--haskell
main = putStrLn "Hello,world!"
これをコンパイルすると、動作が同じプログラムが出来ると思う。haskellの方が容量は大きいけど(ぁ
main関数を読んでない人は、それを参考にするか、フィーリングで理解してね。
Cは明らかに動作を記述している。mainが呼び出されると、printf命令が実行される。
これは明らかに値ではないよね。値がむき出しに書かれているというのはおかしい。
で、どこかに向かって0を返している。
私はCには詳しくないので良く分からないけど、関数の形をしているから必要なんだろう。
そして、問題のhaskellである。
実行結果はCと同じなんで、それだけでは何だかわからないから、GHCiで調べてみよう。
まあ、
printで調べたのと同じ気がするけど。
*Main> :t main
main :: IO ()
そう、mainはIO ()という値なんだ。IOは型の名前だ。型の名前がIOでも、値は値だから、これが何かをしているわけじゃない。
Cのreturn 0と同様、どこかにIO ()を投げているだけだ。
説明書を読むと、これは無視するものなんだろう。
どうも、このソースコードを見ていると、IO ()が評価されると、そのタイミングで評価器がアクションを返すようだ。
haskellは遅延評価だから、呼び出されたときに評価されるわけだし。
そのアクションに対して、haskellのコードは無関係でいられる。だから、副作用がないかのようにソースを書けるんだ。
Cとhaskellのプログラムの動きをまとめてみよう。
C:
- システムがCのプログラムをコールする。
- mainの命令を実行する。中身は次の通りである。
- printfを実行する。
- 値 0 を返す。
haskell:
- システムがhaskellのプログラムをコールする。
- mainの値を評価する。このとき、評価系がmainで評価された値に対して何らかのアクションを起こす。
- mainの値 IO () を返す。
haskellの方は少し怪しいけど、確かに実質的にはCと同じだね。
先ほどCでは全て副作用だと書いたけれど、実質的に副作用を無視した書き方もできる。
haskellも、全く副作用がないけれども、実際は副作用があるかのように書かれる。
こうやって比較すると、haskellの方が変に見えて使いづらそうに思える。
でも、ソースコードを比較して欲しい。haskellの方が簡潔に書かれているよね。
動きは大体同じだと思えば、構造を見るにはhaskellの方が見通しが良いんだ。
さて、今はこれ以上突っ込んだ話が出来ないので、次は合流ポイントである
入出力へ進んでね。
最終更新:2007年09月28日 14:11