カリー化は関数を扱うための高度なテクニックです。 JavaScriptだけでなく、他の言語でも使用されます。
カリー化とは、関数をf(a, b, c)
として呼び出し可能な状態から、f(a)(b)(c)
として呼び出し可能な状態に変換することです。
カリー化は関数を呼び出しません。それは単に関数を変換するだけです。
まず、私たちが何を話しているのかをよりよく理解するために、例を見てみましょう。そして実用的な応用例を見てみましょう。
2引数f
のカリー化を実行するヘルパー関数curry(f)
を作成します。言い換えれば、2引数f(a, b)
に対するcurry(f)
は、f(a)(b)
として実行される関数に変換します。
function curry(f) { // curry(f) does the currying transform
return function(a) {
return function(b) {
return f(a, b);
};
};
}
// usage
function sum(a, b) {
return a + b;
}
let curriedSum = curry(sum);
alert( curriedSum(1)(2) ); // 3
ご覧のとおり、実装は簡単です。2つのラッパーがあるだけです。
curry(func)
の結果はラッパーfunction(a)
です。curriedSum(1)
のように呼び出されると、引数はレキシカル環境に保存され、新しいラッパーfunction(b)
が返されます。- 次に、このラッパーは引数として
2
を使用して呼び出され、元のsum
に呼び出しが渡されます。
lodashライブラリの_.curryなどのより高度なカリー化の実装では、関数を通常どおりにも部分的に呼び出すことができるラッパーが返されます。
function sum(a, b) {
return a + b;
}
let curriedSum = _.curry(sum); // using _.curry from lodash library
alert( curriedSum(1, 2) ); // 3, still callable normally
alert( curriedSum(1)(2) ); // 3, called partially
カリー化? 何のため?
利点を理解するには、価値のある実例が必要です。
たとえば、情報をフォーマットして出力するロギング関数log(date, importance, message)
があるとします。実際のプロジェクトでは、このような関数にはネットワークを介してログを送信するなどの多くの便利な機能がありますが、ここではalert
を使用します。
function log(date, importance, message) {
alert(`[${date.getHours()}:${date.getMinutes()}] [${importance}] ${message}`);
}
カリー化しましょう!
log = _.curry(log);
その後、log
は正常に機能します。
log(new Date(), "DEBUG", "some debug"); // log(a, b, c)
…しかし、カリー化された形式でも機能します。
log(new Date())("DEBUG")("some debug"); // log(a)(b)(c)
これで、現在のログのための便利な関数を簡単に作成できます。
// logNow will be the partial of log with fixed first argument
let logNow = log(new Date());
// use it
logNow("INFO", "message"); // [HH:mm] INFO message
これで、logNow
は最初の引数が固定されたlog
、言い換えれば「部分的に適用された関数」または略して「部分」になりました。
さらに進んで、現在のデバッグログのための便利な関数を作成できます。
let debugNow = logNow("DEBUG");
debugNow("message"); // [HH:mm] DEBUG message
つまり、
- カリー化後も何も失っていません。
log
は通常どおりに呼び出し可能です。 - 今日のログのような部分関数を簡単に生成できます。
高度なカリー化の実装
詳細を知りたい場合は、上記で使用できる複数引数関数用の「高度な」カリー化の実装を次に示します。
非常に短いです。
function curry(func) {
return function curried(...args) {
if (args.length >= func.length) {
return func.apply(this, args);
} else {
return function(...args2) {
return curried.apply(this, args.concat(args2));
}
}
};
}
使用例
function sum(a, b, c) {
return a + b + c;
}
let curriedSum = curry(sum);
alert( curriedSum(1, 2, 3) ); // 6, still callable normally
alert( curriedSum(1)(2,3) ); // 6, currying of 1st arg
alert( curriedSum(1)(2)(3) ); // 6, full currying
新しいcurry
は複雑に見えるかもしれませんが、実際には理解しやすいです。
curry(func)
呼び出しの結果は、次のようなラッパーcurried
です。
// func is the function to transform
function curried(...args) {
if (args.length >= func.length) { // (1)
return func.apply(this, args);
} else {
return function(...args2) { // (2)
return curried.apply(this, args.concat(args2));
}
}
};
実行すると、2つのif
実行ブランチがあります。
- 渡された
args
の数が、元の関数の定義にある数 (func.length
) と同じかそれより多い場合は、func.apply
を使用して呼び出しを渡すだけです。 - それ以外の場合は、部分を取得します。今は
func
を呼び出しません。代わりに、以前の引数と新しい引数を一緒に提供するcurried
を再適用する別のラッパーが返されます。
次に、それを呼び出すと、再び新しい部分 (引数が足りない場合) または、最終的には結果が得られます。
カリー化では、関数が固定数の引数を持つ必要があります。
f(...args)
のような残余パラメータを使用する関数は、この方法ではカリー化できません。
定義上、カリー化はsum(a, b, c)
をsum(a)(b)(c)
に変換する必要があります。
ただし、JavaScriptのカリー化のほとんどの実装は、説明したように高度であり、多引数バリアントでも関数を呼び出し可能に保ちます。
まとめ
カリー化は、f(a,b,c)
をf(a)(b)(c)
として呼び出し可能にする変換です。 JavaScriptの実装では通常、関数を通常どおりに呼び出し可能に保ち、引数の数が十分でない場合は部分を返します。
カリー化を使用すると、部分を簡単に取得できます。ロギングの例で見たように、3つの引数を持つ普遍的な関数log(date, importance, message)
をカリー化すると、1つの引数 (log(date)
など) または2つの引数 (log(date, importance)
など) で呼び出されたときに部分が得られます。
コメント
<code>
タグを使用し、複数行の場合は、<pre>
タグで囲み、10行を超える場合はサンドボックス (plnkr、 jsbin、 codepen…) を使用してください。