uehaj's blog

Grな日々 - GroovyとかGrailsとかElmとかRustとかHaskellとかReactとかFregeとかJavaとか -

関数型とかの論争について

身と蓋がナッシング

まず身も蓋もないことをいうと、結局はCPUの命令セットとして動く言語ですからして、大差はないんですよね。

でもこういうことを言うのは、「どうせ宇宙なんて原子の集合だろ!」という小学生なみです。 なので言わない。でもその認識を捨ててもならない。魔法はないです。地続きではあるのです。

「命令型」か「関数型*1」か

さてその上で、問うべき問いが、「命令型」か「関数型」であるならば、関数型に明確な優位性があらわれます。イミュータブルデータ。いい響きですね。map,filter,...いいですね。for禁止、シビれます。いいか、おまえら! forは禁止だ! 了解しました軍曹殿!

この利点はあきらかで、かつ入手することが比較的容易です。scalaでもJava8でもreactでも、それなりに努力すればそれなりのものが手に入りそうです。

このレベルではOOPとの対立項はないんじゃないかと思います。併用して両方よいとこ取りすればよいでしょう。今そこにある利点。うれしいうれしい。「副作用はほどほどにね!」てなもんです。

(追記)プログラム構成方法論としてOOP vs. FP

関数型とOOPの比較の観点として、たとえばプログラムの構造がクラスの集合として構成されるのか、関数なのか、制御フローがオブジェクトに対するメソッド呼び出しやメッセージパッシングとして構成されるのか、関数や高階関数呼び出しでなのか、という構成方法の問題として語られることもあります。本論では、ここにはあまり主張はなくて、少なくとも極端なのは良くないでしょうよと。原理主義者による信仰化はおひきとりねがいたいし*2オブジェクト指向養成ギブスとかの冗談を真にうけるのもヤメレです。

相互のエミュレーションたとえばScalazとか、HaskellのLensとかObjectiveみたいのもあることだし(理解してないけど)、結局は程度問題に見える。OOPとFPの差と、同じ関数型なら関数型同士の差(正格、非正格はともかく、純粋、非純粋の差は小さくない*3 )、OOP言語の派閥間の差(e.g. Smalltalk vs. C++)を比べると、どっちが大きいかわからなくなりそうだし、Rustみたいにどっちだかわからん言語もおるし。

優劣の問題ではなく、それぞれのスタイルの利点と欠点をちゃんと把握した上でなら、あとは好みも混じえて、解こうとする問題を、適切な労力でとける方を選べばいいじゃんでしょう。

「純粋」関数型の意義

ここらへんから、行方があやしくなってきます。関数型は良いとして、副作用を禁止までして、「純粋」にまでする必要があるのか? モのつくあれが登場してきそうです。

ブコメで純粋関数型は「状態の扱い方が普及のネックになる」旨の指摘を頂きましたが、それはその通りですね。ちなみに、実用レベルの純粋関数型言語が一生懸命にやっているのは、状態を純粋関数から分離し管理するということです。状態を「扱う」ことについては、ある意味非純粋関数以上に意識的に「扱う」のです。状態が、野放しに、プログラムの全体に渡ってばらばらに広がっているのか、そうじゃなくて言語のレベルで、言語機能や型システムのサポートのもとで例外無く強制的に管理するか、という違いです。この状態管理の強制が、純粋関数型を知らない人にとっては、敷居になり得るので、それがいかにわかりやすく普及啓蒙されるか。そのためには、つい出てくる圏論・数学・論理学用語とかがネックになり得るし、コミュニティの文化風土の問題(モヒカン問題)でもあるのかもしれません。ただ、もっと重要なのは、概念が本当に整理され、過剰な一般性や抽象化がなされすぎてはいないか、用語がわかりやすいものになっているかです。今まではそこは全然駄目で、今後、純粋関数型言語が普及していくためには、そこも本気で問われていかねばなりません。

静的でリッチな型システム

「関数型」とは直交し得るので、これが論点なのかすらについても議論があるかもしれないのが、強力な静的型システムをどう評価するのかです。多相型はいいとして、ヒンドリーは見るでしょうか見るなということでしょうか。存在型は、全称量化は、高階多相は、依存型は、リージョン型とか線形型とか幽霊型とかカリーハワード対応とか型レベルプログラミングとかどうなんでしょうか。これらは頭の良いひとたちのタワゴトなんでしょうか。正直、難しすぎやしませんかね。業務で使うとか、どうなんでしょう。我々の目の黒いうちにどうにかなる気がしませんよ。

何を未来に見据えるか?

ポイントは、現在のプログラミング言語の比較の問題ではなく、将来への流れなんだと思ってます。思うに、30年後のプログラミングというのは、極めてリッチな計算資源の元に以下が可能になっているはずです(願望)。

「 効果」「範囲」の明確な解析とそれに元付いたデバッグ、合成、分解、コンピュータによる強い支援を受けたプログラミング

たとえば、プログラムの実行結果として、Yを期待していたのにXという結果が得られたとして、その計算過程の何がYとXの差を生んだのか? 当初Xという結果だったのに、Yという結果になってしまったとき、その間に加えた変更a,b,cのどの操作がその違いを発生させたのか? aだけを適用したら、bだけを適用したら、aとcを適用したら、という順列組合せを実際に瞬時実行し、その結果を産む原因の候補としてのソースコードの変更点が瞬時にリストアップされたらどうでしょう。未来っつーぐらいだら、そうなるべきですよね。ソースの変更は、逐次意味の単位でアトミックに記録され、巻き戻したり、取捨選択して組み合わせたりが、自在にできます。gitのもっと進んだ姿。

あるいはソースを修正後に、再実行なんかはしません。個々のソースの「影響」は、分離されて記録管理されているので、特定箇所の修正は、その範囲が生むであろう「効果」がgitのfast forwardのように再適用されて、更新された実行結果を再表示するだけです*4。もちろん並列処理でのタイミングに基づく非決定性もあってはなりません。

このためには、命令型言語のように副作用を野放しにはできず、非純粋な言語でも駄目です。純粋関数型言語の延長で副作用を明示的管理しなければなりません。また、非決定性の排除が担保できたり、個々の操作の適用を分離して操作可能なのは、強い静的型の元でしかありません。合成や分割などの機械的操作の保証を得られるのも、リッチな型の元でしかありえません。数学的基盤も十全に必要でしょう。

その結果として、初めて、人間は、今は考えられないほど強力なコンピュータの支援の元で、CPUが4個とか16個じゃなく、数千数百万倍のコンピュータパワーが1人の人間がプログラミングする際に独占できるような状況下(そんなことは未来では当然できます)で、deep learningやビッグデータなんかも駆使して、より迅速に、より正しいプログラムを書けるようになるでしょう。

そのような状況に繋がる方向性は、純粋関数型や強いリッチな静的型の延長でしか有り得ません。現在は過渡期としてのそれが存在しているだけなのでその利点が十分には見えていないだけです。

まとめ

まとめると、強いリッチな静的型と結合した純粋関数型プログラミングがより重要な理由は、それが未来だからです。それが未来な理由は、コンピュータからの支援を受けるしか知的作業としてのプログラミングに未来はなく、コンピュータから支援を受ける道を開くのは、意図をより形にあらわせる静的型システムであり、分解結合が容易で副作用を客体化して扱える純粋関数型でしか有りえないからです。「名前重要」とか言ってられるのは相手が人間のときだけです。

目の黒いうちになんとかならないかなー。

*1:本エントリでは、関数型を「関数型プログミング」もしくは「関数型プログラミングスタイル」の意味であいまいに用います。命令型は命令型プログラミング

*2:じゃあ「すべてはクラス」「すべてがオブジェクト」というのがすでに信仰かどうか?ですが、まあ近いとだけ。そんなら「すべてが関数」は信仰か? うーん。うーん。ラムダこりゃ…。

*3:一方で、Haskellも*MLも同じMLファミリと考えることもできる。Erlangはしらない。

*4:こっそり追記するのもあれですが、これは驚異のタイムトラベリングデバッガ、elm-reactorがすでに実装している動作、FRPベースのリロード更新動作、を元に想像しています。