関数型プログラミングと言えば、再帰処理を思い出すでしょう。再帰処理には末尾再帰の有無がスタックオーバーフロー、いわゆるメモリの過消費を防ぐために必要です。末尾再帰について、こちらのwikiをご覧ください。JavaScriptのes6から、末尾再帰を実装できるようになりました。ただし、JavaScriptの世界にわずかのところにしかサポートされていません。V8エンジンがサポートしていないため、nodejsはまずアウトです。ブラウザ側は、後進者のSafari以外サポートしていません。
今のTCO(末尾再帰)のサポート情報: https://kangax.github.io/compat-table/es6/#test-proper_tail_calls_(tail_call_optimisation)
末尾再帰の発動条件
Wikipediaに書いている通り、最後のステップに再帰functionをそのままreturnすることで、再帰以外の計算がなかったら末尾再帰と判断されます。
※ WebKitの仕様はこちら:ECMAScript 6 Proper Tail Calls in WebKit
よく使われる階乗関数を例として。
function iterfact(n) {
if (n === 1n) {
return 1n;
}
// 計算(n*)が入っているので、末尾再帰ではない
return n * iterfact(n - 1n);}
計算を消すために、今まで計算した結果を保持しておくパラメータが必要となります。末尾再帰が有効になっている例はこちらです。
function iterfact(n, acc = 1n) {
if (n === 1n) {
return acc;
}
// iterfactをそのままreturnしているので、末尾再帰と判断される
return iterfact(n - 1n, n * acc);}
末尾再帰をSafariに有効にする方法
上のステップに書いてある再帰functionを計算をかけずにreturnすること以外、strict mode
が有効になっていることも必要です。
以下のようになります。
'use strict';
function iterfact(n, acc = 1n) {
.
.
.
サンプル
下記のCodeSandboxをChromeとSafariで実行してみてください。
Chromeの場合。
Safariなら問題ありません。
回避策
エレガントではないですが、再帰関数を使わない方法はもちろんあります。階乗関数を再帰なしでも簡単にかけるでしょう。
function loopfact(n) {
let result = 1n;
while (n > 1n) {
result *= n;
n--;
}
return result;
}
そうすると、末尾再帰を最適化できないブラウザでもエラーなし動くでしょう。ただし、木構造などのデータを処理するとき、やっぱり再帰の方がわかりやすく思います。
ではでは。