3

I found a recursive expression in a library very confused. The code is here : https://github.com/tappleby/redux-batched-subscribe/blob/master/src/index.js#L22

export function batchedSubscribe(batch) {
  if (typeof batch !== 'function') {
    throw new Error('Expected batch to be a function.');
  }

  const listeners = [];

  function subscribe(listener) {
    listeners.push(listener);

    return function unsubscribe() {
      const index = listeners.indexOf(listener);
      listeners.splice(index, 1);
    };
  }

  function notifyListenersBatched() {
    batch(() => listeners.slice().forEach(listener => listener()));
  }

  return next => (...args) => {
    const store = next(...args);
    const subscribeImmediate = store.subscribe;

    function dispatch(...dispatchArgs) {
      const res = store.dispatch(...dispatchArgs);
      notifyListenersBatched();
      return res;
    }

    return {
      ...store,
      dispatch,
      subscribe,
      subscribeImmediate
    };
  };
}

Specifically this part:

return next => (...args) => {
  const store = next(...args);
  const subscribeImmediate = store.subscribe;

  function dispatch(...dispatchArgs) {
    const res = store.dispatch(...dispatchArgs);
    notifyListenersBatched();
    return res;
  }

  return {
    ...store,
    dispatch,
    subscribe,
    subscribeImmediate
  };
};

How is this not infinite recursion?

m0meni
  • 16,006
  • 16
  • 82
  • 141
  • @Claies I fixed it. I hope the question is answered because it's very intriguing. – m0meni Dec 09 '15 at 03:35
  • What happens when you step through using debugging tools? – Lee Taylor Dec 09 '15 at 03:37
  • Looking only at the bottom snippet; you take the `next` argument (which is presumably a function), and return a function that calls `next` with the args passed to it, as well as doing a bunch of other stuff with the return value of `next`. I don't really see any recursion. – Asad Saeeduddin Dec 09 '15 at 03:41
  • @AR7 No, since `next` is an argument, not the function itself. If I took the function that is being returned in that snippet, and stored it in a variable let's say `f`, I would call it like this: `var g = f(console.log);`. Now `g` is a function that eats an arbitrary bunch of arguments, feeds them to `console.log`, then does a bunch of stuff with whatever `console.log` returns. `g` doesn't call itself, so there's no recursion. – Asad Saeeduddin Dec 09 '15 at 03:51
  • @AsadSaeeduddin yeah I just realized after posting that. I've never seen two arrow functions in a row so I was confused at first. – m0meni Dec 09 '15 at 03:52
  • I missed that arrow too. Thanks @AR7. – Story Maple Dec 09 '15 at 05:20

2 Answers2

3

How is this not infinite recursion?

There is absolutely no recursion here. The syntax next => (...args) => … does not translate to

return function next(...args) {
    const store = next(...args);
    …

but rather to

return function(next) {
    return function(...args) {
        const store = next(...args);
        …

So unless the caller of that function does something weird like var f = batchedSubscribe(…); f(f)(f)…;, it won't call itself.

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

The reason we both seemed confused by this is because the arrow function, if written as a single statement, implicitly calls return.

So for example a simple function like this:

const add = (a, b) => a + b;

is equivalent to

var add = function(a, b) {
  return a + b;
}

Knowing this we can remove the sugar and convert the arrow functions:

return next => function(...args) { // body }

Is really what's going on here, and if we go one step further we get this:

return function(next) {
  return function(...args) {
    const store = next(...args);
    const subscribeImmediate = store.subscribe;

    function dispatch(...dispatchArgs) {
      const res = store.dispatch(...dispatchArgs);
      notifyListenersBatched();
      return res;
    }

    return {
      ...store,
      dispatch,
      subscribe,
      subscribeImmediate
    };
  }
}

Both functions that are containing the code are actually nameless. next is a function, but not one of the functions being returned. It is passed as a variable into the first returned function.

There's no recursion here, but rather a lot of function composition, which is to be expected from a library like redux that draws so much from functional programming.

m0meni
  • 16,006
  • 16
  • 82
  • 141