関数型言語が自然であり当然である世界

手続き型言語が全盛で、関数型言語が、開発効率を変える新しいパラダイムのように言われている……と今更のように言うとずいぶん認識が古いと笑われてしまいそうですが。
関数型言語が後から出てきたのは事実なわけで。えーと、えーと、少なくとも型付きの関数型言語で手続き型同様のパフォーマンスを叩き出せるようになったのってML以降ですよね、ね。LISPコンパイラの中には物凄い最適化を行うものがあるらしいなんて話は忘れてっ。その結果、関数型言語には、refやらIOやら*Worldやら手続き型へのマッピングを行うためのなにかがあるわけです。
じゃあ、関数型言語が先に出来ていたら使われるようになっていたら、どのような世界になっていたのか……。
余談ですが、YTがこのようなことを突然考え出すのは、何かをさぼりながらであることは言うまでもありません。閑話休題
まず、今の手続き型言語はどのように生まれたか。これは勿論、メモリは有限で何度でも上書き可能という前提の元作られたldまたはmovを多用する機械語の命令セットがあり、高級言語からそれを生成するにはメモリを変数として静的にマッピングしてしまうのが一番なので、ローカル変数すらも全部静的で再帰呼び出しができないFortran*1が……と想像できます。
その後スパゲティコードを経て、構造化がなされ概ね今に至ります……抽象データ型だのオブジェクト指向だのなんて些細な問題です。またGCの概念はModulaよりも昔からありましたが*2ポケコンまで行かなくても必死でハイメモリエリアまで使って640kb空間を空けていた16ビット時代を振り返れば、GCのほうが実行効率が良いなんて言っても相手にされなかったのは明白でしょう。当時は速度も大事で文字通りシフトするだけのSJIS変換ですら必死でチェーンしてましたが、メモリはそれ以上に大事で、たとえ仮想アドレス空間が当時存在していたとしても、使わないメモリを確保したままなんてあり得ません。Windowsですらウィンドウクラスという形でウィンドウ間で共有できる情報は共有していた名残が残っていますし、今は誰も使っていないであろう同じ中身の文字列を共有する文字列アトムなんて仕組みまでありました。
メモリという資源が足りないことそのものは、黎明期ではむしろ当たり前でしょう。しかし、これをもって手続き型言語が先に生まれた理由とはなりません。
思考実験です。
メモリが簡単には上書き不可能な作りだったらどうでしょうか。
一度だけは値をセットできるが、クリア状態に戻すには特殊な命令を発行せねばならず、その命令はタイミングを選ぶ上連続するページ(例えば4kバイト)を一掃してしまう……。
ピンポイント書き換えしようと思えば、そのページを丸ごとワークにコピーしておいてからクリアを行い変更を加えながら書き戻すことになり効率悪いこと請け合いです。
嫌でも関数型言語GCが発達しそうじゃないですか?
極初期、コンピュータが計算を行える量は、搭載メモリ量そのままに比例していました。
ループ変数すら扱えないため、コンパイラはループをアンロールするしかなく繰り返し回数を動的に決めることは不可能でした。フレームポインタすら扱えないため動的割り当てが一切できず再帰も不可能でした。勿論変数は一度設定したらそれっきりで、この時期に開発された言語は宣言と同時に初期化を行う構文があたりまえであり、必ず初期化式が与えられる以上、ワードデータだけのモノタイプから多彩なデータ型への移行が起きたその最初から型は書く必要がありませんでした。
やがて、電源を入れなおさなくてもメモリの一部だけリセットを行う技術が整い、コンピュータはずっと動きつづけることができるようになりました。複雑なGCはまだ開発されておらず、それは表面に現れました。各プログラムは割り当てられたメモリを使い終えたら次のプログラムへチェインを行い二度とは戻ってこないのが一般的であり、やがて呼び出し元の個所へ帰ってくるための技として、次のプログラムのために新しく割り当てたページの先頭に復帰位置を書き込んでおくことが行われるようになり、このテクニックは継続と呼ばれ一般化されました。
未使用ページの一部でリンクリストを構成して動的割り当てが行われるようになり、間を置かずして各ページがどこからも参照されていないことを調査するアルゴリズムが開発され、メモリをどんどん使い潰す形でプログラムが書けるようになりました。それでも巨大データ構造を変更のため丸ごとコピーするのは速度の問題があったため、リンクリストが各言語が備えるプリミティブな構造化データ型となりました。配列というデータ構造は固定テーブルのためにしか使われませんでした。それでも速度は遅かったので、計算を必要になるまで実行されない言語も開発されたが、登場が遅かったのと仕組みがややこしいのとで、正格評価の言語を駆逐するには至っていません。
人々はメモリで慣れた概念をファイルシステムに適用したため、ファイルは、一度作成してしまえば、それっきりでした。同名でファイルを作成すると、元のファイルは隠れてしまいますが、消えたわけではなく、元のファイルへのシンボリックリンクは有効でした。
時は経ち、メモリが潤沢に使えるようになり、速度も上がって、ページを丸ごとコピーしても無視できるぐらいの負荷でしかない時代が到達しました。OSがCPUの例外を捕らえて必要な処理をするので、アプリケーションは意識せず何度でもメモリを上書きできるようになりました。
表面上変数が上書き可能であるかのように見せてくれる言語は昔から存在しましたが、効率面で難がありあまり使われてはきませんでした。しかし、内部で無理をしなくて良くなったことで、これらの言語は今になって脚光を浴びるようになっています。
手続き型言語は、初見の人に対し、「再帰を使わずにプログラムを書く方法があるって?」「GCが無くてもメモリを食い潰さずに実行を続けられるって本当か?」「無限リスト無しにどうやってストリームが扱えるんだ?」「えっ、宣言と代入が離れているなんて、そんな面倒なことをするの?」「値が実行中に変わってしまうなんて、そんなので本当にプログラムが書けるの?」「破壊的クイックソートというのがとんでもない実行速度を叩き出すって?」「Replというのを学びたての人が本家より早くKashell6の処理系を書いたって?」など様々な戸惑いを与えているようです。
この流れの背景には勿論非破壊的そろばんの存在がありましたが、寡聞にして私はそれを目にしたことがありません。

*1:昔のBASICの本の記述を鵜呑みにしてます。ざっと検索したところ今はRECURSIVEとか付けたら再帰も可能な様子。

*2:Wikipediaによると1959らしい……いちいち調べてもしょうがないですが。