再帰はプログラミングの基本的なパターンの一つであり、名前付き関数による再帰は直感的で理解しやすいです。しかし、関数に名前を付けずに再帰処理を実現するにはどうすればよいでしょうか?たとえば、即時実行される無名関数内で自身を呼び出す、あるいは再帰ロジックを含むクロージャを返すようなケースです。ここでは、JavaScript の言語特性を活かした無名再帰の実装方法を解説します。
名前付き再帰の確認
まず、標準的な名前付き再帰の例で基礎を確認しましょう。階乗計算を用います:
const factorial = function(n) {
return n < 2 ? n : n * factorial(n - 1);
};
console.log(factorial(5)); // 120
変数経由の再帰(依然として依存あり)
関数式を変数に代入しても、関数内部でその変数名を参照しなければ再帰できません:
const compute = function(n) {
return n < 2 ? n : n * compute(n - 1); // compute という識別子に依存
};
console.log(compute(4)); // 24
これは本質的に名前付き再帰と変わりません。真の「無名性」を保つには、関数自身への参照を動的に取得する必要があります。
arguments.callee を用いた無名再帰
ES5 以前では、arguments.callee を利用することで、関数内部から自身を参照できました。これは、関数オブジェクトへの直接的な参照であり、名前が不要です:
(function(n) {
if (n < 2) return n;
return n * arguments.callee(n - 1);
})(6); // 720
この記法では、関数リテラルに名前を付けることなく、arguments.callee を通じて自分自身を呼び出しています。
代替案:Y コンビネータ(ES5+ 対応)
ただし、arguments.callee は厳格モードで禁止されており、現代の JavaScript では非推奨です。代わりに、高階関数とクロージャを用いた Y コンビネータによる実装が理論的にも実用的にも有効です:
const Y = f => (g => g(g))(h => f((...args) => h(h)(...args)));
const factorialGen = self => n => n < 2 ? n : n * self(n - 1);
const factorial = Y(factorialGen);
console.log(factorial(5)); // 120
このアプローチでは、再帰関数を生成する「ジェネレータ関数」を渡し、Y コンビネータが自己適用によって再帰構造を構築します。名前も arguments.callee も不要で、厳格モード下でも動作します。
実用的な選択肢:再帰用ヘルパー関数
実際の開発では、可読性と保守性を重視し、次のような明示的な再帰ヘルパーを定義するのが推奨されます:
const makeRecursive = (fn) => (...args) => fn((...innerArgs) => makeRecursive(fn)(...innerArgs), ...args);
const factorialImpl = (recur, n) => n < 2 ? n : n * recur(n - 1);
const factorial = makeRecursive(factorialImpl);
console.log(factorial(4)); // 24
このスタイルは、再帰の構造を明示的かつ型安全に保ちつつ、無名関数の文脈でも柔軟に活用できます。