2

我一直在閱讀JavaScript中的遞歸函數和尾部調用優化(TCO)。我的目標是克服在遞歸函數的棧溢出:在JavaScript中封裝尾部優化的實用程序?

function factorial(n) { 
    function recur(n, acc) { 
     if (n === 0) { 
      return acc; 
     } else { 
      return recur(n - 1, n * acc); 
     } 
    } 
    return recur(n, 1); 
} 

factorial(5); // 120 
console.log(factorial(4585759)); // Maximum call stack size exceeded 

我已經找到了如何使用thunktrampoline克服遞歸函數的棧溢出:

let thunk = function (fn) { 
    return function() { 
     let args = Array.prototype.slice.apply(arguments); 
     return function() { return fn.apply(this, args); }; 
    }; 
}; 

function trampoline(f) { 
    while (f && f instanceof Function) { 
     f = f(); 
    } 
    return f; 
} 

function factorial(n) { 
    let recur = function(x, n) { 
     if (n === 0) { 
      return x; 
     } else { 
      return thunk(recur)(n * x, n - 1); 
     } 
    }; 
    return trampoline(thunk(recur)(1, n)); 
} 

console.log(factorial(5)); // 120 
console.log(factorial(4585759)); // Infinity 

然而,我不喜歡我被迫寫遞歸函數的方式。我發現了一個函數的一些實現命名TCO

function tco(fn) { 
    var active, nextArgs; 
    return function() { 
    var result; 
    nextArgs = arguments; 
    if (!active) { 
     active = true; 
     while (nextArgs) { 
     result = fn.apply(this, [nextArgs, nextArgs = null][0]); 
     } 
     active = false; 
    } 
    return result; 
    }; 
} 

功能應該允許如下:

let factorialToc = tco(function(n) { 
    function recur(n, acc) { 
     if (n === 0) { 
      return acc; 
     } else { 
      return recur(n - 1, n * acc); 
     } 
    }; 
    return recur(n, 1); 
}); 

但它不工作:

factorialToc(5); // 120 
console.log(factorialToc(4585759)); // Maximum call stack size exceeded 

是否有任何實用功能封裝TCO?

+3

'return n * factorial(n - 1);'是*不是*尾部調用。你永遠不會得到一個優化。 – Bergi

+0

我已經更新了階乘的實現,它現在應該工作嗎? –

回答

2

您將tco函數應用於非遞歸函數factorialToc,而不是recur導致堆棧溢出。它應該是

function factorialToc(n) { 
    const recur = tco(function(n, acc) { 
     if (n === 0) { 
      return acc; 
     } else { 
      return recur(n - 1, n * acc); 
     } 
    }); 
    return recur(n, 1); 
}