JavaScriptのクロージャと部分適用は、「引数の一部だけを与えて別の関数を作る処理」という理解だけだと落とし穴があります。
意外にもこの2つを比較して書いている書籍やウェブページが見当たらず、それどころかまったく同じものだと書いてあるサイトもありました。
同じ間違いを犯す人が少しでも減ることを願い、この記事を書くことにしました。
前提
- 当然ながらブラウザ・Node.jsのどちらでも同じです。TypeScriptでも同様です。
- 筆者はアマチュアエンジニアかつQiita初心者なので、間違いはお手柔らかにご指導願います。
結論
- クロージャの閉包された引数は「変数」のように処理される。
- 部分適用で与えられた引数は「定数」のように処理される。
具体例
遠足に持っていくおやつは300円までというルールがあるとします。
買い物の際にルールに違反していないかを調べる関数を作ってみましょう。
クロージャの場合
const budgetClosure = budget => price => {
budget -= price;
return budget >= 0;
};
const budgetCheck = budgetClosure(300);
budgetCheck(200); // true
budgetCheck(120); // false
200円のおやつを買った後に120円のおやつを買うことはできないという判定になりました。
クロージャに入れたbudgetの値は変数のように処理されるので、priceを引くたびに新しい値に更新されるようです。
部分適用の場合
const budgetBind = (budget, price) => {
budget -= price;
return budget >= 0;
};
const budgetCheck = budgetBind.bind(null, 300);
budgetCheck(200); // true
budgetCheck(120); // true
どうやら300円以下のおやつであればいくらでも買えてしまうようです(その手があったか)。
部分適用でbudgetに入れた300円は関数内部で定数のように処理される、と考えてよいと思われます(ちなみに筆者はこちらでも変数のように動くと錯覚していました)。
なお、オブジェクトを引数とする関数に対して部分適用するケースでは、オブジェクトのメンバーは変数のように処理されます。
「constはあくまで再代入の禁止なので、オブジェクトのメンバーは上書きできる」ということを考えれば納得できるでしょう。
const budgetBind = (budget, price) => {
budget.amount -= price;
return budget.amount >= 0;
};
const budgetCheck = budgetBind.bind(null, {amount: 300});
budgetCheck(200); // true
budgetCheck(120); // false
補足
- (部分適用とは似て非なるものとは理解しているものの)カリー化された関数はどちらの動きになるのか疑問でした。結果は部分適用と同じで、定数として扱われるようです。
- 筆者は他の言語をあまり理解していませんが、Wikipediaによると言語によってはクロージャの動作は異なるようです。関数型言語では不変性が重要視された結果ということでしょうか。