読者です 読者をやめる 読者になる 読者になる

関数型言語が普及しない理由

設計

えーとですね...。

 というわけで本稿を書くわけですが(ヤメテ!そんな冷たい目で僕を見ないで!)関数型言語*1についてはよく知りませんので、決して真に受ける事無く、オブジェクト指向言語をようやっと使っている底辺プログラマのぼやきということで了解いただければと思います。(ヤメテ!その前置きはズルイとか言わないで!)


■ 1. そもそも関数型言語が全く分からない。
 僕は、関数型言語オブジェクト指向言語と何が違うんだってことがずっと分からないのです。僕に限らず Java ばかりやっているプログラマには分からない人も多いと思うんですよね。これはもう根本的な問題で、そもそも関数型言語の何が優れているか分からないから関数型言語を使う事が出来ないんですよ。パターンマッチ、高階関数、カリー化、メソッドチェインとかを紹介されても、"それで短く書けたり関数が抽象化されることの何がそんなに嬉しいの?"になるし、遅延評価は確かに良いのかもしれないけど"ちょっと頑張れば Java でも出来る気がする"。参照透過性と言われても"情報隠蔽のコンセプトはどこへ行ったの?"になる。副作用が無いって言われても"リストに要素追加するだけで副作用なのでダメと言われても困る""だいたい関数ばかりの言語よりも関数とデータがセットになってるオブジェクト指向の方が良いに決まってるじゃないか"と思う。となると、もしかして"理解できない理解できないと言われているモナドがある言語こそが関数型言語だとでも言うの?"
 となって、結局 "関数型言語っていう、最近涼しい顔して歩いてる生意気なアレは一体何なんだよksg!" に落ち着くんじゃないかなと思います。



■ 2. なぜ僕らはオブジェクト指向言語なら分かることが出来たのか?
 さて、何故 C スタイルの構造化言語全盛期にオブジェクト指向言語が受け入れられたのは何故でしょうか?

 C スタイルの構造化言語には明らかな限界がありました。(C++ で書きますね)

struct Even {
    int data;  // data には必ず偶数が入っている
};
void foo( Even & ev ) {
    print( ev.data ); // ev は正しく初期化されているだろうか?
    ev.data = 5;      // 奇数が代入できるけど誰も注意してくれない。
}

↑これではプログラマが留意すべき点が多すぎてエンバグしそうです。そこでオブジェクト指向の出番です。

class Even {
private:
    int data;
public:
    Even( int data )     { set( data ); } // コンストラクタでデータを正しくセットする。
    void get()           { return this->data; }
    void set( int data ) { this->data = ( ( data % 2 ) == 0 )? data: data - 1; }
};
void foo( Even & ev ) {
    print( ev.get() ); // コンストラクタで初期化されているハズだから安心
    ev.set( 5 );       // メンバ関数が面倒みてくれるハズだから安心
}

良いですねー。さすがオブジェクト指向ですねー(棒。

この二つのソースコードは、凄く似ている気がします。Java はやっぱり C と似ているんです。つまり構造化言語を書いていたプログラマ達からオブジェクト指向言語はすぐ近くに見えていた↓んですよ。*2

しかしオブジェクト指向言語から関数型言語は見えない↓のです。

恐らく何か大きなパラダイムシフトでこの距離を飛びこえないといけないと思うんですよ。ここを飛び越えるためには、例えば "パターンマッチが簡潔に書ける!" とかそういう細かい話ではダメです。落ち着いて、一歩下がって、プログラミングというものを見直してみます。



■ 3. そもそもプログラムとは副作用を起こすためのものである。
 プログラムは計算をしてその結果を出力します。例えば宣伝用の Web ページの表示であったり、「会員登録完了しました!」という内容のメール送信であったり、画像レタッチの Undo 操作だったりするわけです。これら正に副作用ですよね。

 副作用を起こすためにプログラムというものは存在しているのです。

 計算だけして出力の無いプログラムなんて、1 円の価値も無いのです。

 やった!

 副作用は金になる!!

 副作用バンザイ!!!

 副作用で書かれたオブジェクト指向バンザ . . . . . .ん ?


 そう、僕は、オブジェクト指向言語関数型言語の距離を埋めるポイントはココだと思うんです。プログラムは副作用を起こすために存在しているのですが、それ以外の部分は副作用で書く必要など無いのです。この視点こそがオブジェクト指向言語から関数型言語へ飛び移るチケットではなかろうかと思うのです。



■ 4. 副作用が無いからこそ可能な設計がある。
 例えば 配列に含まれている全ての素数を求める関数を考えます。

vector< int > get_primes( vector< int > v ) {
    vector< int > ret;
    for( int i = 0; i < v.size(); ++ i ) {
        if( is_prime( v[ i ] ) ) {
            ret.push_back( v[ i ] );
        }
    }
    return ret;
}

 ↑この関数は、全ての素数を計算するまで終わりません。しかし、欲しいのは先頭の 3 つだけかも知れないですし、100 以上のもの全てかも知れないです。前者のために「○個求めたら計算を止める」という意味の引数を増やしましょうか?それとも「○○以上の値だけを取り出す」という意味の引数を増やしましょうか?
 ダメです。汎用性が下がりそうです。エンバグしそうです。素数を求める関数は、素数を求めるだけに留めておきたいです。この関数を使ってやりたいことはシンプルなハズです。

もし仮にこんな↓感じでソースコードが書けて、必要なぶんだけ計算をしてくれるなら、良いと思いませんか。


 result = ( v ) の中で ( 素数のもの ) の中で ( 先頭の 3 つ );
result = ( v ) の中で ( 素数のもの ) の中で ( 100 以上のもの全て );
 プログラムの構造が変わったように見えます。僕はこれこそが関数型への質的な変化だと思うのです。例えば↓こんな言語は存在しませんが、自然と関数型言語っぽい見た目になってゆくと思いませんか。

result = ( v ) -> ( is_prime ) -> take 3;
result = ( v ) -> ( is_prime ) -> ( >= 100 );
 しかし計算の途中で、例えば v のサイズが変わったりでもしようものなら、何だかぐちゃぐちゃになりそうです。絶対に v の内容が変わらないという保証が必要です。その保証こそが「副作用なし」ということですきっと。また、もし仮に is_prime 関数はグローバル変数の値によって結果が変わるようならやっぱりぐちゃぐちゃになりそうです。絶対に is_prime 関数は外部の状態に依存しないという保証が必要です。その保証こそが「参照透過性」ですきっと

 このように、関数型言語ひたすら副作用の無いものを組み合わせて全体を組み立て、副作用は局所化して扱うようにプログラミングするための言語ですきっと。副作用をだらだらと書き続ける、いわゆる手続き型言語との根本的な違いが見えてきました。こういう背景を踏まえれば、カリー化も高階関数もパターンマッチも遅延評価も末尾再帰もモナドも、その他あらゆる関数型言語的な技法が価値を持っていることが分かるような気がします。


■ 5. なぜ関数型言語は普及しないのか?

 冒頭にも書きましたが僕は関数型言語が分かりません。
 しかし、もしここまで書いてきた予想が正しいのであれば、関数型言語が普及しない理由はプログラマの思考とズレがあるからだと思います。

 プログラミングはコンピュータに命令することです。「あれをして、それをして、その結果を持ってきてこれを実行しろ」とプログラミング言語で書くわけです。まさに副作用と状態依存のカタマリです。副作用の無い、参照透過な計算の集合としてプログラムを設計する能力は皆が持っているものでは無いのです。そういうセンスの無い(僕みたいな)連中は、やっぱり手続き型言語を選ぶと思うのです。言葉は悪いですがそういう人の方がきっと多いのです。だから流行らないと思います。

 とはいえ。

 素数プログラムの例のように関数型言語の機能が有用なケースはたくさんあります。なので、副作用だらけの言語に関数型言語の機能を取り入れる(インストールする)事例は増えそうです。あと、オブジェクト指向言語ではちょっとオブジェクトを組み合わせて設計するだけですぐに複雑怪奇でぐちゃぐちゃなプログラムになります。オブジェクト指向言語だって正解では無いのです。

 他方、もしここまで書いてきた予想が間違っているのであれば、関数型言語が普及しない理由はやはり根本的に理解できないからだと思います。冒頭で @bleis センセがつぶやいているように「俺が分からないから」が多数派を占めるから普及しないのです。


元ネタ 『なぜ関数型言語は普及しないか』プログラミング日記 『関数型言語が普及しない理由』mizon dev 『関数型言語が普及しない理由その2』mizon dev

*1:本稿では元 post に従って関数型プログラミング言語を「関数型言語」、それに合わせてオブジェクト指向プログラミング言語を「オブジェクト指向言語」、構造化プログラミング言語を「構造化言語」と書きますね

*2:それは本当のオブジェクト指向言語では無い!という突っ込みはここでは置いときます