キマイラ・サイトは http://www.chimaira.org/です。
トラックバック/コメントは日付を気にせずにどうぞ。
連絡は hiyama{at}chimaira{dot}org へ。
蒸し返し歓迎!
ところで、アーカイブってけっこう便利ですよ。タクソノミーも作成中。
2005-12-13 (火)
「続きを読む」はもう使わない、かも
日常 | |
読む人が余りいそうにない長いエントリは、「続きを読む」を使うと親切かな、と思って「コンパクト空間と論理/モデル論」で使ってみた。一般論としては、この配慮はいいことだろうが、僕自身には嬉しくなかったわ。
一画面に10日分も表示しているのは、一般論としてはよろしくないけど、自分に便利だからだった。オフライン状態でも、ここ10日間のエントリを見られるもんね。「続きを読む」を使うと、このメリットがなくなる。
JavaScriptでカリー化
雑記/備忘 | |
JavaScriptでカリー化。ありがち、つうか実際にあるでしょうね。小ネタと思ってやりはじめたら、意外と混乱した。一種のメタプログラミングのはずだが、実際にはテキスト加工処理。
内容:
- カリー化ってなに?
- カリー化を行う関数を作る:準備
- カリー化を行う関数を作る:テキストのパッチワーク
- カリー化を行う関数を作る:組み立て
●カリー化ってなに?
2引数の関数f(x, y)に対して、「gがfのカリー化」だとは、f(x, y) = g(x)(y) が常に成立すること -- ゴチャゴチャ説明するより実例実例:
functio sum(x, y) {
return x + y;
}
このsumのカリー化の例:
function curried_sum(x) {
return function (y) {return sum(x, y);}
}
curreid_sum関数は1引数で、戻り値として関数(これも1引数)を返します。実行してみると:
js> var x = curried_sum(10) js> x(15) 25 js> curried_sum(10)(15) 25 js> curried_sum(13)(-20) -7 js> var f = sum; var g = curried_sum js> f(123, 999) == g(123)(999) true js>
最初に出したカリー化の定義がなんとなくはつかめたでしょう。もとの関数の引数が2つ以上あっても同様です。
functio mean3(a, b, c) {
return (a + b + c)/3;
}
function curried_mean3(a) {
return function (b, c) {return mean3(a, b, c);}
}
js> var y = curried_mean3(10) js> y(20, 30) 20 js> mean3(10, 20, 30) == curried_mean3(10)(20, 30) true js>
●カリー化を行う関数を作る:準備
sum → curried_sum、mean3 → curried_mean3は人の頭と手で行ったのですが、これを関数にやらせましょう。つまり、var curried_sum = curry(sum); var curried_mean3 = curry(mean3);として自動的にカリー化を作り出す関数curryを定義するのです。
これを行うためには、もとの関数を加工して新しい関数を作り出すことになります。小手調べに、与えられた関数の第1引数、残りの引数、関数定義本体を、テキストとして抜き出す関数を作ってみます。
function decompFun(fun) {
if (typeof fun != 'function') {
throw new Error("The argument must be a function.");
}
if (fun.arity == 0) {
throw new Error("The function must have more than one argument.");
}
var funText = fun.toString();
var args = /function .*\((.*)\)(.*)/.exec(funText)[1].split(', ');
var firstArg = args.shift();
var restArgs = args.join(', ');
var body = funText.replace(/function .*\(.*\) /, "");
print("firstArg:" + firstArg);
print("restArgs:" + restArgs);
print("body:" + body);
}
js> decompFun(sum)
firstArg:x
restArgs:y
body:
{
return x + y;
}
js> decompFun(mean3)
firstArg:x
restArgs:y, z
body:
{
return (x + y + z) / 3;
}
js>
こうして取り出した第1引数、残りの引数、本体をつぎはぎして、新しい関数を定義するテキストを作ればいいわけです。
●カリー化を行う関数を作る:テキストのパッチワーク
具体例と共に考えるため、sumのカリー化をもう一度出します。
function (x) {
return function (y) {return x + y;}
}
これは最初のcurried_sumとはちょっと変更点があります。
- function宣言文ではなくて、function式として関数を直接表現している。
- sumという関数名の代わりにsumの定義本体を埋め込んでいる。
これを眺めれば、カリー化の一般形が予測できますね。
function (<第1引数>) {
return function (<残り引数>) <本体>
}
このパターンに従って、文字列連結で組み立てましょう。
var curriedText =
"function (" + firstArg + ") {" +
"return function (" + restArgs + ")" + body +
"}";
こうして作ったcurriedTextはあくまでテキストです。このテキストに対応する本物の関数を作って戻すのがcurryの仕事です。
●カリー化を行う関数を作る:組み立て
今まで準備した素材を使ってcurry関数全体を定義しましょう。テキストから関数を作るにはevalを使います。
function curry(fun) {
if (typeof fun != 'function') {
throw new Error("The argument must be a function.");
}
if (fun.arity == 0) {
throw new Error("The function must have more than one argument.");
}
var funText = fun.toString();
var args = /function .*\((.*)\)(.*)/.exec(funText)[1].split(', ');
var firstArg = args.shift();
var restArgs = args.join(', ');
var body = funText.replace(/function .*\(.*\) /, "");
var curriedText =
"function (" + firstArg + ") {" +
"return function (" + restArgs + ")" + body +
"}";
eval("var curried =" + curriedText);
return curried;
}
うーん、テキストつぎはぎは、やっぱダサイな -- いいんか? これで。
js> curry(sum)(10)(15) 25 js> curry(mean3)(10)(20, 30) 20 js>
curry(curry(sum))(10)()(20)は動きますが、JavaScriptのスコーピングの都合で curry(curry(mean3)(10))(20)(30)はうまく動かないようです。
[追記 date="12-14"]「もっとかしこいカリー化」も見てね。[/追記]
[追記 date="12-15"]理屈も好きなかたは、「カリーをもっと -- ラムダで考えるカリー化」もどうぞ。[/追記]
テスト -- はてなの変な現象
日常 | |
「変」じゃなくて、これは「親切」なのかな。「<」のエスケープを忘れると、こんなことになるんだ、フーン。
js: "<stdin>", line 18: uncaught JavaScript runtime exception: js: "<stdin>", line 20: uncaught JavaScript runtime exception:
- JavaScript で引数束縛
- http://d.hatena.ne.jp/kilrey/20051213
- http://d.hatena.ne.jp/m-hiyama/20051214
- http://d.hatena.ne.jp/m-hiyama/20060412
- http://d.hatena.ne.jp/wataru87/20070114
- 檜山正幸のキマイラ飼育記 - CPS(継続渡し方式)変換をJavaScript...
- ましまろ日記 - [C++]カリー化?
- Computer Science勉強日記 - prototype.jsを読もう11
- javascript - 弾もcurryを煮込んでみた
- rokujyouhitomaの日記 - カリー化
- お前の血は何色だ!! 4 - ただのクロージャとカリー化の違いがよくわ...
- polygon clip - JavaScriptでカリー化 - 檜山正幸のキマイラ飼育記
function curry(f) {
return function (arg1) {
return function () {
var args = [arg1];
for (var i = 0; i < arguments.length; i++) {
args.push(arguments[i]);
}
return f.apply(this, args);
};
};
}
既にご存知かもしれませんが、これ作るのに参考にしたページです:
http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference
これも動きました。意外と完璧かもです。
「咳をしたら一人じゃない」(実際、いま咳している、風邪)