Javaによる関数型プログラミング、その1

2018年8月13日

※このシリーズの全投稿は/tag/関数型プログラミングにあるので参照されたい。

Javaによる関数型プログラミングについてまとめてみるのだが、しかし、Haskellやら何やらの純粋関数型プログラミングはいじったことも無いし、Scalaは7年ほど前に一つの製品を出しただけでほとんど忘れてしまった。これは自分のためのメモ書きのようなものである。ここではいわゆるモナドについても触れていく予定だ。

間違い、勘違い、不適切部分もあるかもしれないことはあらかじめお断りしておく。

なぜ関数型プログラミングをするのか?

ウェブ検索をしてみると、この点を全く置き去りにしている説明がほとんどであり、初心者は「なんでそんなものが必要になるのか?」が全く理解できないだろう。「そんなもの使わずとも、どんなプログラムでも作れるじゃないか」という感想を持つに違いない。

実は私もそうであった。

この関数型プログラミングが脚光を浴びる理由は、「これ以上コンピュータの性能は上がらない」が大きいのである。もちろん、この場合の「コンピュータ」とは現状のPC等のことであって、量子コンピュータは含まない。

※別の理由もあるのだが、話の都合上ここでは置いておく。

コンピュータの性能はこれ以上上がらない

わざと引っ掛け的な文言にしてみたのだが、「んなバカな」と思うかもしれない。しかし、現実としてそうなのだ。ここで言いたいのは、CPUのスピードがこれ以上には上がらないということだ。

これは光や電気の最高速度に限界があることに起因している。光速度は秒速30万キロメートルだったと思うが、ほとんどその限界に近づいているのだ。

簡単な計算をしてみよう。光速度(あるいは電磁波)が秒速30万キロであるとする。現代のコンピュータに搭載されるCPUのクロックは、速いものでは4Ghz程度だ。つまり、一秒間に4,000,000,000回、時計としてのチクタクをしているわけだ。

では、300,000,000メートルを、4,000,000,000回で割るといくらになるだろう?0.075メートルだ。つまり、4GHzというクロックのもとでは、光(電子)でさえ1クロックあたり7.5cmしか進めないのだ。もちろん、実際にはこのスピードより落ちてしまう。真空ではなく物質中を通るからだ。

つまり、これ以上クロックを速くすると、もはやCPU内部の配線といった微小な世界であっても、ノロシか何かのように「ゆっくりとした」伝わり方しかできないことを意味している。こちらの配線とあちらの配線の長さが異なれば、信号の伝達時間が違ってきてしまい、同時に到着することさえできないのだ。

そして、当然だが物理的に光の持つ制限速度以上に速くすることはできない。いかんともしがたい物理法則の壁がある。

これでわかると思う。クロックスピードをこれ以上には上げられないため、CPUが速くなることは絶対にない。

マルチコアとスレッド

そこで出てきたのが、いわゆるマルチコアである。単純な発想だ。一つのCPUパッケージの中に別々に動作するCPUを複数入れるというだけである。それらは全く別のことを並行して同時に行うことができる。

しかし、大昔に作られたプログラムはこれを有効利用することができない。なぜなら、大昔のプログラムというのは、「一つずつ順番に実行する」ことを前提としていて、「複数の事柄を同時に一斉に実行する」ということにはなっていないからだ。

これを例えれば、一人の人間が何かをすることに似ている。同時に複数のことはできないのだ。大根を切ることと、薪割りをすることは同時にできない。「これをやって、次にあれをやる」というのが昔のプログラムの作られ方だ。

一方で、マルチコア(あるいはマルチプロセッサ)というのは、さしづめ複数の人間が同時に動けるようなものである。

しかし、これをプログラムするのは難しい。スレッドを使わねばならないからだ。

Javaでスレッドを扱った者であれば、すぐにわかると思うのだが、スレッドを使う場合には、同期というものを常に気にしないといけない。

例えば、一塊の変数群にアクセスする場合、どのスレッドがいつどのようにアクセスするかわかったものでは無いので、それを調停するための仕組みが必要になってくる。Javaであればsynchronizedやらセマフォと言ったものを使う。

しかし、このスレッドという面倒な仕組みを使ってプログラミングしない限り、マルチコア、マルチCPUの恩恵は受けられない。。。と、思われてきたのだが。。。

関数型プログラミング

そこで出てきたのが、関数型プログラミングだ(もちろん大昔から関数型プログラミングというのはあったようだが、ここ10年位のあいだ脚光を浴びているわけだ)。

そもそも、前述したスレッドの問題点としてはこうだ。

  • 各スレッドで共有しているリソース、つまりその状態の変化が問題となるようなものにアクセスする際の調停が面倒

ということである。複数のスレッドにおいて共有している状態が変化してしまうということがそもそもの問題の発端である。

であれば、「状態なんか排除しちゃえばいいんじゃね?」というのが関数型プログラミングの発想だ。

正直むちゃくちゃな発想である。なぜなら、状態を完全に排除することはできないからだ。例えば、今現在プログラムによって画面に表示されているものも「状態」だし、データベースに格納されて保持されているものも「状態」だ。

しかし、そのあたりは今の時点ではいったん脇においておく。で、状態を排除するとは一体どういうことなのだろうか?

(続く)