2007年11月23日金曜日

JavaScript と高階関数

JavaScript における高階プログラミング という記事を読みました。
高階プログラミングとは高階関数を使ったプログラミングのことです。

高階関数とは?

高階関数について、以下のような記述があります。
プログラミング言語において、関数を引数にしたり、あるいは関数を戻り値とするような関数の事である。引数や戻り値の関数もまた高階関数となり得る。これは主に関数型言語やその背景理論であるラムダ計算において多用される。数学でも同様の概念はあり、汎関数と呼ばれる。Wikipedia -- 高階関数
関数を引数にしたり、関数を戻り値とする手法って、Ruby や JavaScript では当たり前に使います。
例えば、クロージャを使うって関数を返り値として使う例を見てみます。
function multiple(a){
return function(num){
return a * num;
}
}

// 5 を掛ける関数を生成
var multi5 = multiple(5);

alert(multi5(4));
///// 20

alert(multi5(5));
///// 25
また、次の例のように、関数を引数に取るパターンとして比較関数の例をあげることができます。
// 座標を保持するオブジェクト
function Point(x, y){
this.x = x;
this.y = y;
}

var a = new Point(2, 8);
var b = new Point(5, 6);
var c = new Point(3, 3);

// y 座標が大きい順に並べ替える比較関数を使ってオブジェクトを並べ替え
console.log([a, b, c].sort(
function (x, y) {
return y.y - x.y;
}
);
///// [Object x=2 y=8, Object x=5 y=6, Object x=3 y=3]
今回は高階関数の以下の特徴をそれぞれ見ていきたいと思います。
  • 関数を引数として使う
  • 関数を返り値として使う

関数を引数として使う

html を生成するプログラムを考えます。まず高階関数を使わないで書く場合、
var arr = ['text1', 'text2', 'text3'];
var html='';

for (var i = 0, l = arr.length; i < arr.length; i++) {
var item = arr[i];
html+= '<p>' + item + '</p>';
}

document.getElementById('comment-form').innerHTML = html;
///// <p>text1</p><p>text2</p><p>text3</p>
関数を引数にとる高階関数の考え方を使って同様の動作をするプログラムを書くと次のようになります。
 // 引数の関数を Array の要素それぞれに適用し、
// 結果を文字列として連結する関数
Array.prototype.reduce = function(templateFunction) {
var str = '';
for (var i = 0, l = this.length;i < l; i++) str += templateFunction(this[i]);
return str;
}

// 配列の要素にそれぞれ適用
function prettyTemplate(item) {
return '<p>' + item + '</p>';
}

document.getElementById('comment-form').innerHTML = (['text1', 'text2', 'text3'].reduce(prettyTemplate));
///// <p>text1</p><p>text2</p><p>text3</p>

関数を返り値として使う

関数を返り値として使う高階関数の考え方を使って上記の例を書き直してみると以下のようになります。
function wrapP(){
return function(item){
return '<p>' + item + '</p>';
}
}

['text1', 'text2', 'text3'].reduce(wrapP());
///// <p>text1</p><p>text2</p><p>text3</p>
この例の wrapP 関数をクロージャを使ってもう少し抽象化すると、次のように任意のタグで囲むことができるようになります。
function wrap(tag) {
var s_tag='<'+tag+'>';
var e_tag='</'+tag.replace(/s.*/,'')+'>';

return function(x) {
return s_tag + x + e_tag;
}
}

['text1', 'text2', 'text3'].reduce(wrap('p class="test"'));
///// <p class="test">text1</p><p class="test">text2</p><p class="test">text3</p>