I'm refactoring some code and wondering what pattern is least memory intensive and easiest to read when it comes to passing constants in a recursive function.
For example, I could just pass each constant down to the next recursive function, but it isn't obvious that those params are constants:
const startFoo = (myArray, isFoo, isBar) => {
console.log(isFoo, isBar);
startFoo(myArray, isFoo, isBar);
};
Alternatively, I could have 2 functions and keep constants in the closure of the first, but I'm curious if recreating the second function every time the first is called will cause unnecessary object creation & GC:
const startFoo = (myArray, isFoo, isBar) => {
const foo = myArray => {
console.log(isFoo, isBar);
foo(myArray);
};
foo(myArray);
};
Finally, I could keep it at one function, and just cache the initial values:
const startFoo = (myArray, isFoo, isBar) => {
if (!startFoo.cache) {
startFoo.cache = {
isFoo,
isBar
}
}
const {isFoo, isBar} = startFoo.cache;
console.log(isFoo, isBar);
startFoo(myArray);
};
All 3 look like they'll be good candidates for the upcoming (here-ish) TCO so I don't see that playing into the decision, but if it does that'd be good to know as well!
Incase base condition or exit condition is not specified in the function then recursive calls to the function can lead to an infinite loop.
Like the robots of Asimov, all recursive algorithms must obey three important laws: A recursive algorithm must call itself, recursively. A recursive algorithm must have a base case. A recursive algorithm must change its state and move toward the base case.
Absolutely, yes. Tree evaluators are almost always written as sets of recursive functions.
it isn't obvious that those params are constants
Does it matter that much? If you are choosing recursion over a loop because you like the functional approach, all your variables and parameters are constants anyway. You can tell whether they stay constant or not in the recursive descent by looking at the recursive call and comparing the arguments to your function's parameters.
Alternatively, I could have 2 functions and keep constants in the closure of the first, but I'm curious if recreating the second function every time the first is called will cause unnecessary object creation & GC.
Unlikely. Afaik, no function objects are instantiated for simple inline helper functions that are never used as an object or exported as a closure. At least it's a rather trivial optimisation, and even when not done the GC pressure will not be hard or impair performance noticeably.
You should go for this approach as it is the cleanest and most maintainable.
I could keep it at one function, and just cache the initial values
You better not do that. It introduces an extra condition in the function that will impair performance because it is executed on each and every call, but most of all it complicates the code unnecessarily much. This is also likely the reason for the bug you introduced - you never unset the .cache
in the base case1 so that all invocations will use the same constants no matter what is passed to them. Also, you are leaking the constants into the global scope where anyone could access them.
1: Admittedly, your demo function does not have a base case, but ask yourself: if you had added one, would you have forgotten to unset the cache?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With