みなさまこんばんは。
前回に引き続き、平松 @co_sche (co-sche)です。

この連載では、先日社内で行われた「HTML5, CSS3を舐め回す会 Vol.2 - JavaScript Day -」で私がお話した「JavaScriptで始める関数型プログラミング」の内容を元に、記事にまとめたいと思います。

想定している読者は、
  • JavaScriptの基本的な構文を理解している方
  • JavaScriptで関数を定義・使用したことのある方
  • LLと総称される言語を扱っているが、関数型プログラミングを意識したことのない方
です。

はじめに

前回は、関数型プログラミング(以下、たまにFP)の定義から関数とはなんぞやというところまで掘り下げて学びました。
今回は、もう一つの下ごしらえとしてFPにおける関数の特徴を学びましょう。

いっちょまえな関数を知る

FPを学ぶ上で重要な「関数」の特徴、扱いをここで抑えておきましょう。
FPにおける関数について、「第一級関数」「関数が第一級オブジェクト」と表現されることがあります。
これは、関数を数値や文字列等の値と同等に扱えるということを示しています。
どういうことか、他の値と比較しながら見ていきます。

リテラル表現 (値そのものを表す)

数値そのものを「1」や「2」や「0xffcccc」、文字列を「"ロマンス"」と表現するのと同じように、関数そのもの(関数値)を示す記法があります。
前回も紹介しましたが、
function(a) { return a + a; }
です。関数そのものなので、名前もありません。

ところで、行頭にfunctionキーワードがあるとJSでは関数宣言文として解釈されます。
関数宣言文の場合は、お馴染みかと思いますが
function 関数名 (引数群…) { }
というようにfunctionキーワードと引数を括るカッコの間に関数名を書く必要があります。
関数リテラルのつもりで書くと、関数名が無く、いきなり"("に遭遇したよという構文エラーになってしまいます。
このため、宣言文では無く式であることを明示するために
(function(a) { return a + a; })
とカッコで括って書くことが多いです。
以降、関数リテラルのみを表記する際にはカッコで括りませんが、試して見るときには必要に応じてカッコを補ってください。

その場で適用(実行)するなら、
(function(a) { return a + a; })(3);
となります。

変数に代入

前回、「厳密なFPに代入はありません」と言ったばかりですが、上記リテラル表現の意味をより理解するためにはこれも紹介するべきでしょう。
文字列の場合、代入は
var label = "ハネムーン";
です。同様に、関数も
var double = function(a) { return a + a; };
と表記できます。上記リテラル表現と併せて見て、関数値を代入しているという意味をかみしめて下さい。
functionキーワードの位置が行頭ではないので、リテラルを( )で括らなくても式と評価されますが、括っても構いません。
関数の実行結果ではなく、関数そのものを代入していることに注意してください。
double(2); // 4
と適用(実行)して、結果を得ることができます。

ここで、λ計算について少し。
λ計算でも関数に名前を付けることはありますが、それは単純に紙面を節約するための名前だと考えて下さい。
いつでも元の関数に戻して書き起こせるぞ、というスタンスです。
以降のJSコードでも関数に名前を付ける際に代入を行いますが、このスタンスに則ります。

引数として受け取る・渡す

数値を引数として渡すのは、
double(5);
という字面になりますが、同様に関数値を引数として渡すことも可能です。
$(function() { console.log('loaded!'); }); // Document
jQueryを利用したことのある方にはお馴染みの表記かと思います。
そうでない方も、「$」という関数を、関数値を引数として渡して実行していることを確認して下さい。
引数に渡している関数値に名前をつけて、
var f = function() { console.log('loaded!'); };
$(f); // Document
としても意味は全く変わりません。

これらは、$関数が関数を(も)受け取ることが出来るように定義されているからこそ可能な表現です。
と言っても、関数を引数として受け取るのに特別な記法は要りません。例えば、
function(f, a) { return f(a) + f(a); }
このようになります。

そのまま実行するなら
(function(f, a) {
  return f(a) + f(a);
})(function(n) {
  return n * n;
}, 10); // 200
です。関数値の表現に改行・インデントが入りましたが、混乱せずに読み解いて下さい。

それぞれの関数に代入を使って名前をつけるなら、
var g = function(f, a) { return f(a) + f(a); };
var h = function(n) { return n * n; };
g(h, 7); // 98
となります。こちらの方が読み解き易かった方は、最下行のg,hを、それぞれ上2行の代入文の右辺で置換してみてください。「そのまま実行」の時とほぼ同じ形になります。

もっと例を挙げましょう。
g(function(s) {
  return s.length;
}, "かみました"); // 10

g(function(a) {
  return a.join('e');
}, 'dltr'.split('')); // "deleterdeleter"


戻り値として返す


数値を戻り値として返す関数は、今まで何度も出てきていますが
function(n) { return n * n; }
と表現されます。もっと直載的に
function() { return 2; }
という関数があってもいいでしょう。この関数は、引数として何も受け取らず、2という数値を返す関数です。

名前をつけるなら、
var createTwo = function() { return 2; };
となります。しつこいようですが、関数の実行結果ではなく、関数そのものを代入していることに注意して下さい。
値を取り出すには、
createTwo(); // 2
と、実行する必要があります。

では、関数を戻り値として返す関数を記述してみましょう。
function() {
  return function() {
    return 2;
  };
}
です。
数値を返すときとあまり変わりはなく、returnキーワードの後ろに関数値が入ります。
名前をつけるなら、
var createCreateTwo = function() {
  return function() {
    return 2;
  };
};
となります。見ての通り、createCreate2は「2を返す関数を返す関数」です。

実行してみましょう。
createCreateTwo(); // function() { return 2; }
中身の関数が返されます。

名前をつけて、さらに実行します。
var anotherCreateTwo = createCreateTwo();
anotherCreateTwo(); // 2
いっぺんに実行することもできます。
createCreateTwo()(); // 2

応用編として、「引数として受け取った数値を返す関数を返す関数」を定義・実行してみましょう。
var createCreateN = function(n) {
  return function() {
    return n;
  };
};

var createFour = createCreateN(4);
createFour(); // 4
createCreateN(5); // function() { return 5; };
createCreateN(6)(); // 6
(function(n) { return function() { return n; }; })(7)(); // 7
ややこしく見えますが、評価される様子を1行1行読み解いてください。

高階関数

上記の「引数として関数を受け取る関数」や「戻り値として関数を返す関数」をまとめて、高階関数と呼びます。FPの説明で頻出する用語ですので、意味と一緒に覚えておいてください。

高階関数を定義・使用する際に注意したいのが、関数の型(引数・戻り値の型を合わせたもの)です。
例えば、
function(f, a) { return f(a) + f(a); };
のfには、「任意の型の値aを受け取って、+演算が使える型の値を返す関数」が期待されています。

逆に言うと、それだけの制限しかありません。JSでは関数の型を宣言出来ない上に、暗黙の型変換が強力に効くので、
(function(){}) + (function(){}); // "function () {}function () {}"
という無茶も出来てしまいます。
また、高階関数の場合は直接呼び出すわけではないので、「関数の型」が見えづらいということも挙げられます。

だからこそ、高階関数を定義する際は、関数を引数として取る・戻り値として戻すことだけでなく、その関数の型が何であるかが分かりやすいコードを書きましょう。ドキュメントに書くという手もあります。
また、使用する際はそれを的確に読み取るようにしましょう。

今回のまとめ

はい、関数型言語においては数値や文字列などと同等に関数も値なんですよーというお話でした。

関数型言語では…
  1. 関数そのものを示す表記(リテラル)がある。
  2. 関数を関数の引数として渡す・受け取ることができる。
  3. 関数を関数の戻り値として返すことができる。
  4. 2,3のような関数を高階関数と呼ぶ。
  5. 関数の型(引数の型・戻り値の型)は重要。
「関数の実行結果」と、「関数そのもの」の違いを認識しコードから見分ける必要があるということも今回の隠れ重要ポイントです。

最後に

かなりくどくどと例を挙げて書きましたが、最後の方の説明まで「そんなの当たり前じゃん」を維持して読み進めていただけたなら本望です。

次回は、実務にありがちな例を挙げて1週目にお約束したメリットを具体的に説明したいと思います。
お楽しみにっ!

--

この連載では偉そうに講釈してますが、実は私も勉強中だったりします。
自分が分かりにくかった点を噛み砕いて可能な限り正確に説明しているつもりですが、万一間違いやトンデモ理論が飛び出した時はAmeba Pocket等ブックマークコメントでビシバシツッコミをいただければ幸いです。


JavaScriptで始める関数型プログラミング 関連記事
JavaScriptで始める関数型プログラミング 1 - 1
JavaScriptで始める関数型プログラミング 1 - 3