このブログの更新は Twitterアカウント @m_hiyama で通知されます。
Follow @m_hiyama

メールでのご連絡は hiyama{at}chimaira{dot}org まで。

はじめてのメールはスパムと判定されることがあります。最初は、信頼されているドメインから差し障りのない文面を送っていただけると、スパムと判定されにくいと思います。

参照用 記事

JavaScriptの実験:高階関数、mixinなど

最近、JavaScript人気がすごいなー。1年前には(少なくとも僕は)想像もできなかった事態です。

「ブラウザのなかで動く奇妙な言語」という印象しか抱いてなかったのですが、ナカナカのもんみたいですね。遅ればせながら実験。ブラウザだと試行錯誤やデバッグに向かないから、Rhinoの対話的環境でやってみました。

まずは、ラムダ式風の関数定義、可変(個数)引数、高階関数などの見本として関数totalを定義してみました。僕の意図としては、関数totalのプロファイル(型仕様)は:


(number | number[]), (number -> number), (number -> boolean)
→ number

これを言葉で説明すれば:

  • 第1引数は、数値 または 数値の配列
  • 第2引数は、1つの数値引数を取り数値を返す関数
  • 第3引数は、1つの数値引数を取り真偽値を返す関数
  • 戻り値は、数値

さらに、これらの引数は省略可能です(だから、引数仕様を正確に書くと面倒な正規表現になる)。

定義:


/*
引数numbers : numberまたはnumber[] を期待
ただし、デフォルトフィルターは数値以外をふるい落とす。
引数operation : number -> number を期待
引数filter : number -> boolean を期待

numbersの項目をfilterでふるいにかけてからoperationを施した値を
すべて足しあげる。
*/
function total(numbers, operation, filter) {
var defaultOperation = function(n) {return n;}
var defaultFilter = function(n) {return (typeof n == 'number');}

switch (arguments.length) {
case 0:
return 0;
case 1:
operation = defaultOperation;
filter = defaultFilter;
break;
case 2:
filter = defaultFilter;
break;
default:
break;
}
if (typeof numbers == 'number') {
var n = numbers;
numbers = new Array();
numbers[0] = n;
}
var t = 0;
for (var i = 0; i < numbers.length; i++) {
if (filter(numbers[i])) {
t += operation(numbers[i]);
}
}
return t;
}

実行:


js> total()
0
js> total(3)
3
js> total(3, function(n){return n*n})
9
js> total([3, 4, 5])
12
js> total(["three", 4, 5])
9
js> total([3, 4, 5], function(n){return n*2})
24
js> total([3, 4, 5], function(n){return n*n}, function(n){return (n % 2 == 1)})
34
js>

次は、mixin(mixiじゃないよ)みたいなもの。オブジェクトの能力や振る舞いを後から変更できます。僕は、クラスや継承が好きじゃないので、こういうほうがかえって自然に思える。

定義:


/*
引数obj : オブジェクトを期待
引数defs : 注入可能なメソッド群を持ったオブジェクトを期待
引数override : boolean を期待

defsに定義されているメソッドをすべてobjにコピーする。
同名のプロパティが既にあるときは、overrideが真の場合だけコピーする。
*/
function injectMethods(obj, defs, override) {
switch (arguments.length) {
case 0:
case 1:
return;
case 2:
override = false;
break;
default:
break;
}
for (var f in defs) {
if (typeof defs[f] == 'function') {
if (typeof obj[f] == 'undefined' || override == true) {
obj[f] = defs[f];
}
}
}
}

実行:


js> Arith = {
sum : function(n, m) {return n + m},
mul : function(n, m) {return n * m}
}
[object Object]
js> obj = {}
[object Object]
js> obj.sum(2, 3)
js: "", line 699: uncaught JavaScript runtime exception: TypeError: sum is not a function.
js> injectMethods(obj, Arith)
js> obj.sum(2, 3)
5
js> obj.mul(2, 3)
6
js>

この関数は、外からオブジェクトにメソッド群を注入しますが、Objectのプロトタイプに同じような関数をセットしておけば、能力/振る舞いのインクルード機能をすべてのオブジェクトで使えます。

定義:


Object.prototype.includeMethods = function (defs, override) {
switch (arguments.length) {
case 0:
return;
case 1:
override = false;
break;
default:
break;
}
for (var f in defs) {
if (typeof defs[f] == 'function') {
if (typeof this[f] == 'undefined' || override == true) {
this[f] = defs[f];
}
}
}
}

実行:


js> obj = {}
[object Object]
js> obj.sum(2, 3)
js: "", line 757: uncaught JavaScript runtime exception: TypeError: sum is not a function.
js> obj.includeMethods(Arith)
js> obj.sum(2, 3)
5
js> obj.mul(2, 3)
6
js>