7

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!

Matt K
  • 4,813
  • 4
  • 22
  • 35
  • For me 1st approach seems good enough!!! – Parag Bhayani Jun 13 '16 at 13:57
  • 4
    I generally have the outer function be a context for constants and utility functions, and then have the *real* recursive function also nested there. A call to the outer function initializes whatever needs to be initialized, and then it starts the recursive process. Tends to save a lot of unnecessary parameter passing. – Pointy Jun 13 '16 at 13:57
  • I would like to know one thing, in 3rd approach startFoo.cache is a global variable??? – Parag Bhayani Jun 13 '16 at 14:00
  • @Pointy you're talking about Option #2, correct? Any concerns about repeatedly creating & trashing that inner function if the outer one is called a bunch? Parag, no globals, just a self referencing. – Matt K Jun 13 '16 at 14:04
  • 2
    But you're *not* repeatedly creating and trashing the inner function during the recursive process. Creating a function is no more expensive than creating an object; the parser just has to parse the code once, and instantiating the function instance after that is cheap. Just one function instantiation for each invocation of the recursive process is not a significant cost. – Pointy Jun 13 '16 at 14:07
  • @MattK Is this just some theory stuff or are you really caring in practice about that? You won't even notice any memory difference by choosing different patterns there. Tbh I would go with the most **readable** way here and **not with the best performance**. – Xatenev Jun 13 '16 at 14:12
  • @Pointy any references on how V8 instantiates functions? I get that the inner function isn't being recreated while in recursion, but if the outer function is called 1000x in a for loop, then I'd be instantiating that inner function 1000 times. – Matt K Jun 13 '16 at 14:12
  • @Xatenev absolutely, at this point it's almost purely stylistic – Matt K Jun 13 '16 at 14:13
  • @MattK http://thibaultlaurens.github.io/javascript/2013/04/29/how-the-v8-engine-works/ – TylerH Jun 13 '16 at 14:15
  • @Pointy: great explanation, is there any way to clear or delete closure function? to avoid garbage collection? – Parag Bhayani Jun 13 '16 at 14:17
  • `const startFoo = (x) => { x++; console.log(x); }` - a const declared function variable does not make its parameters const - or did I misunderstand something about #1 ? – le_m Jun 13 '16 at 14:24

1 Answers1

4

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?

Bergi
  • 630,263
  • 148
  • 957
  • 1,375