当ブログのタイトルは、「関数型プログラミングとは何かを簡単に説明しようと思ったんだけどやっぱり簡単ではなかった」というタイトルが長すぎたので、短くしたものです。(詐欺じゃん!(爆))
職場で「“関数型プログラミング”の“関数”って何?」って聞いたところ、自分が知らなかった・忘れていた視点があったので、考えていたこと・教わったことをメモしておきます。
ちなみに、自分は関数型プログラミングについては全く詳しくないので、以下、間違っている点や独自研究wとなっている事があるだろうと思います。
まず、「関数」という文字について。
元々は「function」の訳語として「函数」という字が当てられたそうで。これが中国語の発音でもfunctionに近いらしく、けっこう良い訳語らしい(笑)
しかし日本では常用漢字化に伴い、「函」という字が使えなくなったので、「関」が割り当てられたのだとか。
次に「関数型」という言葉について。
「関数型プログラミング」と言わずに「関数プログラミング」と言え、という人がいるみたいで。
たぶん、プログラミング界隈の用語に「型(type)」というものがあるので、「関数型」と聞いたら「関数を表す型(function type)」というものを想像しそうで、しかしそういう型があるわけじゃないから、型という単語を使うなという主張なのかなぁと思う。
しかしそれだと、「関数型」は「functional」の訳だから、「関数」だと「function」で、「al」がどこか行っちゃうw
個人的には、「オブジェクト指向」に倣って「関数指向」と呼んでみたらどうかなぁと思った。
ただ、オブジェクト指向は「Object Oriented」で「指向(oriented)」という語が元々入っているのに対し、関数指向だとそうじゃないんだよねぇ。
まぁそれはともかく、「関数型プログラミング言語」だと長いので、「関数型言語」と略すのは自然だと思う。「関数言語」という略し方は見たことが無いな^^;
さて、関数型プログラミング言語の特徴としてよく挙げられる事柄のひとつに、「関数が第一級オブジェクトである」というものがある。
ここで言う「オブジェクト」は(オブジェクト指向とは全く無関係で)、「プログラミング言語がサポートしている要素」というようなニュアンスかな。
Java8のStream APIやScalaのコレクションでmapやfilterというメソッドに「関数」を渡して処理していくスタイルを、自分は関数型プログラミングの典型だと思っていた。
この為にはメソッド(サブルーチン)の引数に関数を渡せる必要があり、これにはプログラミング言語のサポート(専用の構文)が必要となる。
例えば(Java7以前の)Javaでは「処理」を渡そうと思ったらそういう処理を持つオブジェクトを渡す形になっていた。このままでもコレクションのmapやfilterといったメソッドを呼び出す形式のライブラリーは作れるが、プログラミング言語のサポート無しでそういうコーディングをするのは面倒だ。
ところで、「○○プログラミング言語」は「○○を使ったプログラミングをやりやすくする言語」だと思う。
例えばオブジェクト指向プログラミングは、オブジェクトをサポートしていない言語でも出来る、という話がある。そりゃ、初期のC++はコンパイルするとC言語のソースを出力していたわけで、C言語でもオブジェクト指向プログラミングは出来る。けれど、やはりオブジェクトを扱う機能があってこそ、オブジェクト指向プログラミング言語を名乗れると思う。
なので、関数プログラミング言語なら、関数をサポートするのが条件だと思える。そういう意味で、「関数が第一級オブジェクトである」というのは関数プログラミング言語の唯一の特徴でしょう?(ドヤ)
しかしここで登場するのがJavaScript!
JavaScriptでは関数をサブルーチンの引数に渡すことが出来る。(主にコールバック関数として使う(イベントが発生したときに「渡した関数」が呼ばれる))
あるいはC言語でも関数ポインターという形で、サブルーチンの引数に渡すことが出来る。
ではC言語やJavaScriptが関数プログラミング言語かと言われると、個人的にはしっくり来ない。関数プログラミング言語の定義が「関数をサポートしている」ならば、合っているんだけどね…。
自分が関数型プログラミングの関数の使い道としてまず思い浮かべるのは、mapやfilter。すると、mapやfilterを使うライブラリーがあればいいのかな?
しかし前述の通り、「関数」が無くてもそういったライブラリーは作れる。
ライブラリーの有無でプログラミング言語全般の特徴を語るのも変だしなー。
別の視点として、関数型が何かを考えるに当たり、関数型でないものと比較してみるのは良さそう。
そこで出てきたのが手続き型(オブジェクト指向ではなく)。それは考えたことが無かった!
手続き型プログラミングは、「“状態を持つもの”に対し、その状態を変更する手続きを記述する」というスタイル。
状態を変更する手続きというのは、状態を変える命令(コマンド)を順番に発行するといった事になるらしい。そして、最終的な状態が得られる答えになる、と。
(ちなみに、“状態を持つもの”を格好良く言うと、“チューリングマシン”らしい)
(でも、オブジェクト指向プログラミングも、まさにこれだな。状態を持つもの=オブジェクトに対し、それを変更する手続き=メソッドを呼び出す)
では、これに対する関数型プログラミングがどうかと言うと、「サブルーチンは渡された引数だけを使って演算して答えを返す」「それを次のサブルーチンの引数に渡す」というスタイル。
引数の値が同じなら常に同じ値を返すとか、条件はもう少しあるみたいだけど。いわゆる参照透過性のことだね。個人的には「副作用が無い」と言う方をよく使うけど。
参照透過性を満たすサブルーチンのことを「関数」と呼ぶことになるのかな。
(状態を持たない)関数を呼び出していく方式なので、手続き型とは全く異なる。
こちらの考え方を採ると、第一級オブジェクトとか、何の関係も無い(爆)
副作用の無いサブルーチンにするというコーディングはどの言語でも出来るし、言語のサポートというのも特に必要としない(C++にはconstという機能があるけど)。あくまでコーディングスタイルであり、それ用の言語というものは無い、ということになる。
そして、この考え方に立つと、JavaScriptが関数型プログラミング言語だと言われても違和感があった理由が分かる。コールバック関数は副作用が無いどころか、副作用を起こすためのものじゃんw(コールバックの呼び出し元に何かを返す為のものではない)
一方、mapやfilterに渡す「関数」は、引数を受け取り、それだけを使って値を返すもの。こちらは参照透過性の考えとずれていない。
つまり、自分の意識の中では、関数型プログラミング(言語)という概念に参照透過性を含めていたわけだ。
元々自分が考えていたのは
「○○プログラミング言語」は「○○を扱うプログラミング」をサポートする言語である。 「関数型プログラミング」は「関数を使うプログラミング」である。 なので、関数型プログラミング言語の条件は、関数が扱えること(関数が第一級オブジェクト)である。 mapやfilterを持つコレクションは、関数が第一級オブジェクトであることを利用したライブラリーである。だったが、今は、
関数が第一級オブジェクトである→関数プログラミング言語 参照透過性を考慮したプログラミングスタイル→関数型プログラミング(「言語」は無い) この定義だと、「関数型プログラミング」と「関数(型)プログラミング言語」は一致しない。 mapやfilterを持つコレクションは、関数が第一級オブジェクトであることと参照透過性があることを利用したライブラリーおよびコーディングスタイルである。という感じになった^^;
やっぱ混乱するわ~orz
コーディングスタイル(考え方)の話と、プログラミング言語の要素の話が同じ(似た)言葉で混ざっているのがよくないねー。
結局、教えてもらった一連の話の中で唯一確定したのは、「関数プログラミング言語」「関数型プログラミング」の「関数」は、数学の「関数」ではない、ということ。
プログラミング言語の「関数」の語源はもちろん数学の「関数」だろうけど、数学の関数には厳密な定義があり、それは参照透過性みたいな考え方とも違うのだそうだ。