Created
January 29, 2012 03:46
-
-
Save Gozala/1697037 to your computer and use it in GitHub Desktop.
Workaround for lack of "tail call optimization" in JS
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Lack of tail call optimization in JS | |
var sum = function(x, y) { | |
return y > 0 ? sum(x + 1, y - 1) : | |
y < 0 ? sum(x - 1, y + 1) : | |
x | |
} | |
sum(20, 100000) // => RangeError: Maximum call stack size exceeded | |
// Using workaround | |
var sum = tco(function(x, y) { | |
return y > 0 ? sum(x + 1, y - 1) : | |
y < 0 ? sum(x - 1, y + 1) : | |
x | |
}) | |
sum(20, 100000) // => 100020 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function tco(f) { | |
/** | |
Takes `f` function and returns wrapper in return, that may be | |
used for tail recursive algorithms. Note that returned funciton | |
is not side effect free and should not be called from anywhere | |
else during tail recursion. In other words if | |
`var f = tco(function foo() { ... bar() ... })`, then `bar` | |
should never call `f`. It is ok though for `bar` to call `tco(foo)` | |
instead. | |
## Examples | |
var sum = tco(function(x, y) { | |
return y > 0 ? sum(x + 1, y - 1) : | |
y < 0 ? sum(x - 1, y + 1) : | |
x | |
}) | |
sum(20, 100000) // => 100020 | |
**/ | |
var value, active = false, accumulated = [] | |
return function accumulator() { | |
// Every time accumulator is called, given set of parameters | |
// are accumulated. | |
accumulated.push(arguments) | |
// If accumulator is inactive (is not in the process of | |
// tail recursion) activate and start accumulating parameters. | |
if (!active) { | |
active = true | |
// If wrapped `f` performs tail call, then new set of parameters will | |
// be accumulated causing new iteration in the loop. If `f` does not | |
// performs tail call then accumulation is finished and `value` will | |
// be returned. | |
while (accumulated.length) value = f.apply(this, accumulated.shift()) | |
active = false | |
return value | |
} | |
} | |
} |
@Gozala. Thanks for your sharing. This is a little tricky at the first sight. I've got one question: Is this line active = false
at the accumulator()
really necessary?
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@ypocat I meant we all prefer a certain code style and others stink :)