では「再帰を安全に使う工夫」について解説します。性能比較で見たように、再帰は便利だけど「深すぎる呼び出し」でスタックオーバーフローを起こす危険があります。そこで登場するのが 末尾再帰最適化 や トランポリン手法 です。
再帰を安全に使う工夫
1. 末尾再帰(Tail Recursion)
- 定義: 関数の「最後の処理」が再帰呼び出しになっている形。
- 一部の言語(Scheme, Scala, Rustなど)では「末尾再帰最適化」が働き、ループと同じ効率で実行できる。
- 残念ながら JavaScriptは標準で末尾再帰最適化を保証していない ので、深すぎる再帰は危険。
例(末尾再帰版の階乗)
function factorialTail(n, acc = 1) {
if (n === 0) return acc; // 終了条件
return factorialTail(n - 1, n * acc); // 最後に再帰呼び出し
}
console.log(factorialTail(5)); // 120
JavaScript👉 acc(累積値)を引数で持ち回ることで、戻りながら計算する必要がなくなる。
👉 ただしJSでは最適化されないので、深すぎるとやはり危険。
2. トランポリン手法(Trampoline)
- 考え方: 再帰関数が「次に呼ぶ関数」を返すようにして、実際の呼び出しはループで行う。
- こうすると「関数呼び出しの深さ」が1で固定され、スタックオーバーフローを防げる。
例(トランポリン化した階乗)
function factorialStep(n, acc = 1) {
if (n === 0) return acc;
return () => factorialStep(n - 1, n * acc); // 関数を返す
}
function trampoline(fn) {
while (typeof fn === "function") {
fn = fn();
}
return fn;
}
console.log(trampoline(() => factorialStep(100000)));
// 大きな数でもスタックオーバーフローしない
JavaScript👉 trampoline が「関数を返す関数」をループで実行してくれるので、深い再帰も安全に処理できる。
3. 再帰をループに書き換える
- 実務では「深さが大きい処理」はループに書き換えるのが一般的。
- 例: 配列の合計、階乗などはループの方が高速で安全。
✅ まとめ
- 末尾再帰: 戻り値の最後に再帰を置く → 一部言語では最適化される
- トランポリン: 再帰を「関数を返す関数」に変えてループで処理 → JSでも安全
- ループに変換: 実務ではシンプルで効率的
👉 ここまでで「再帰の安全な使い方」まで押さえられました。


