技術をかじる猫

適当に気になった技術や言語、思ったこと考えた事など。

何かズレた説明を見つけたので補正

電車の中で適当に眺めてたら、何か違う主張をしているブログが見つかったので、何をどう「違う」のかを検証してみる。

http://d.hatena.ne.jp/tanakmura/touch/20090429/1240996946

彼の主張は

誰が言い出したのか知らないが…世の中には関数型言語が並列プログラミングの次世代だとかいう主張を見かけることがある。
間違いとは言えないが、ちょっと誇張しすぎだろ、という話。

これはその通りだと思う。ただ、そこに行き当たるまでの論理が間違っているような気がした。

まず、最初にちょろっと書かれている、特殊な記法やライブラリについて、ここは特に反論はない。強いて挙げるなら、並列処理をする場合はそれが明示されるという点で、DSL*1でも無い限りどの言語でも並列化する所で専用の記法はある。
記法がださいだのなんだのは単に嫌いなだけだろうと解釈しておく。このへんは宗教にしかならん。

次に、挙げているところが

どこを並列化すれば速くなるのかを探す
安全に並列化できる場所はどこにあるかを探す
で、僕の主張は、関数型言語はこれら二点を解決してくれるものではない、というものである。

というところだ。この2点のうち、「安全に並列化できる場所はどこかを探す」の説明が関数型の「副作用排除」とその効用に矛盾している。
致命的に違うと考えているのは「安全に並列化できる場所はどこにあるかを探す」という点だ。真に正しく書かれている純粋関数型言語のコードにおいては、そんなものを探すことが本来間違いなのだ。
それを説明するために、先ずは参照透過性について説明する。

これは、Wikipediaの「副作用」にまとまっている通り、

同じ条件を与えれば必ず同じ結果が得られる
他のいかなる機能の結果にも影響を与えない

つまり、計算する上で、「Aという引数で計算したら、決定的な計算結果としてかならずBという値を返す」ということが、並列だろうと、前の結果がどんな結果になろうと、何万回実行しようと、環境変数変わろうと、変数が変わろうと不変の鉄則となっていることを意味する。
もしその性質が真であるのなら、並列で動いたって個々の結果は変わらないし、お互いに影響が存在しない。なら、並列処理でガンガン走らせればいいじゃない!という事になる。そのため、

「副作用のある言語では、色々なんかごちゃごちゃしてるので、依存性がわかりにくいが、副作用が無いと依存性がわかりやすいので並列化しやすいですよ」

はそもそもの前提が壊れている。その参照透過性が成立するのであれば、彼の主張している「依存性がどこにあるかを探す」の部分は、そもそも依存性が無いのだからどこでも並列化すればよろしい。
彼の挙げている「関数ポインタ」も、純粋な関数型言語であれば、そもそも関数ポインタ自体存在しないし、基本は関数スタックに関数の実体が積まれるだけで、そういった問題は発生しない。(そのために、大量の関数スタックを保持するための独自の実装をhaskellは行なっている)さらに純粋関数型言語には変数すら存在しない。*2

なので下記の彼の結論は、関数型言語を正しく理解していないがための間違いである。

実際、この「依存性の解析」という点において、「純粋関数型が副作用のある言語に比べて有利である」、とは思えない。

さて、私の反論はここまでだ。此処から先は補足した上で同調してみる。

じゃぁ関数型ですべて解決できるのかというとそうではない。純粋関数型であるHaskellはその鉄則を守らせるために、モナド*3というものを発明した。個人で楽しむならまだしも、業務ではとても簡単に理解できる・してもらえる代物ではないし、関数の返り値が複雑奇っ怪なことになる(正確に全部記述しようなんてものなら、可読性に難が出るとかそういう次元の話ではなくなる)。
加えて、そこまでステートレスなシステムで何ができるのよ?という問題もある。例えばWebで言えばログイン判断にセッションを使う作りが多いが、セッションも状態であるので、真に線形透過性があるならそんなもの参照するのはNGだし、同様の理由でDB含む外部リソース、PCのステータス、ディスクアクセス、すべてNGになってしまう。*4

Wiki にもあると思うが、下記がひとつの解なのだ。

反面副作用を持たない言語設計はノイマンアーキテクチャと反りが合わず、効率の点で不利になることが多い。また単純な逐次処理を行う場合は状態を中心に命令的な思考をした方が扱いやすい場合がある。

要するに純粋な関数型は実用的ではないのだ。結果、実用となる言語は少なくとも「副作用のあるコードが書ける」言語となってしまう。
しかし、副作用がない、参照透過性を持つステートレスなシステムは前に述べたように並列化しても特に問題は発生しない。なので、極力そういったシステムにしていけば、スケールアップはやりやすいし、マルチコアシステムでパフォーマンスを出しやすいのも事実だ。

更に加えて言えば、マルチスレッドにおける参照透過性は同じ単語は出てこないまでも、C#でもJavaでも言われている話なので、ぶっちゃけ言語に依存する話ではない。単に関数型言語がそういう文化圏であるがゆえにそう書きやすい言語仕様をしているだけのことだ。

事実は事実として、何言語でもそういうコードは大体書けるのだから極力そうすべきなのだろうし、だからこそF#やScalaなんて(純粋ではない)数型言語が最近力をつけている*5

*1:DomainSpecificLanguage、詳細はぐぐれ

*2:全てはWriteAtOnceな定数だ

*3:http://ja.wikipedia.org/wiki/%E3%83%A2%E3%83%8A%E3%83%89 や http://blogs.dion.ne.jp/keis/archives/5880105.html

*4:語弊がないように追記するが、関数型言語は状態を持てない訳ではない、状態の表現が変数ではなく、関数スタックの積まれ方の差として出てくるだけなのだ。死ぬほどイメージしづらいけど、、、

*5:http://jp.techcrunch.com/archives/20120912javascript-tops-latest-programming-language-popularity-ranking-from-redmonk/