10

What's the difference between doing these two solutions to the same problem?

Closure Method

const numberIncrementer = startValue => () => startValue++
const getNextNumber = numberIncrementer(0)

console.log(getNextNumber())
// 0

console.log(getNextNumber())
// 1

console.log(getNextNumber())
// 2

Generator Method

const numberIncrementer = function*(startValue) {
    while(true) {
        yield startValue++
    }
}

const numberFactory = numberIncrementer(0)
const getNextNumber = () => numberFactory.next().value

console.log(getNextNumber())
// 0

console.log(getNextNumber())
// 1

console.log(getNextNumber())
// 2

Viewing these two methods, what reason do I have to choose one over the other?

Kevin Ghadyani
  • 6,829
  • 6
  • 44
  • 62
  • Well the syntax is completely different? And try to do anything a bit more complex than an infinite loop… – Bergi Sep 08 '17 at 15:33
  • 1
    There is tons of overlap in many APIs. In this example, the differences aren't really shown -- especially since it appears a closure is being created by the generator. The main differences come up in terms of async flow. While you have the `.next()` method hard coded into your `getNextNumber` function, it would perhaps more appropriately be outside of that function so you can call `next()` after some `async` work returns with data. But i could be wrong about all of this... – Pytth Sep 08 '17 at 15:34
  • possible duplicate of https://stackoverflow.com/q/45093404/1048572 or https://stackoverflow.com/q/23613612/1048572 – Bergi Sep 08 '17 at 15:36
  • 1
    Functionally speaking, one big difference is that Generators produce [`Iterators`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator), so you can use `for..of` to iterate over the data a generator produces. Another difference as that with the Generator you could process an infinite sequence. – Rob M. Sep 08 '17 at 15:39
  • There cannot be any difference in this case because the final result is a function that returns ever-increaing number. The same applies to `var foo = 1` vs `var foo = incrediblyComplexFunctionThatReturns1()` or any other comparison where KISS principle is applicable. – Estus Flask Sep 08 '17 at 15:57
  • 3
    Closures are sort of higher order functions and hence very powerful. It wouldn't surprise me, if they are equally expressive as generators in many scenarios. A closure provides state between function calls, which is stored on the heap and can consume (arguments) and produce (return) values. Sounds a lot like generators, at least technically. It's probably awkward in complex scenarios to maintain state like generators do, though. –  Sep 08 '17 at 18:15
  • 1
    To me it seems like comparing apples with oranges. Generators are basically syntactic sugar for creating iterators. Closures are functions that have free variables. Generator functions can be closures. – Felix Kling Sep 08 '17 at 21:01
  • What do you think happens when you do `const numberFactory = numberIncrementer(0)` ..? The `numberFactory` function is being assigned with an iterator function which has access to the value within the generator function. It uses closures. So Generators work on the basis of closures but they are invented to introduce the abstraction of laziness to JS. – Redu Sep 10 '17 at 17:08
  • @Redu Lazyness doesn't abstract anything. It is an evaluation strategy. Since functions/lambdas are nothing more then named/anonymous, lazy evaluated expressions, all you need in Javascript to express lazy evaluation are functions. –  Sep 11 '17 at 12:17
  • @Bergi So generators are essentially syntactic sugar for stateful closures? I mean they seem to be really helpful as coroutine engines, but are they mandatory? –  Sep 11 '17 at 12:29
  • @ftor Yes, exactly that. You can instantiate stateful iterator objects with `next` methods in any way you like, but generators provide very powerful syntactic sugar for that. What do you mean by "mandatory"? – Bergi Sep 11 '17 at 12:32

2 Answers2

13

In the following contrived example I convert an Object into a Map without relying on an intermediate Array:

function* entries(o) {
  for (let k in o) yield [k, o[k]];
}

const m = new Map(entries({foo: 1, bar: 2}))

console.log(
  Array.from(m) // [["foo", 1], ["bar", 2]]
);

This doesn't work with a closure, as you cannot access the state of the for/in loop to maintain it across function calls. The state is encapsulated within the loop construct. So there are cases that can be expressed with generators but not with closures.

2

Adding to a previous answer: while there might be cases that can be expressed with generators but not with closures (I was looking for that answer as well), the one demonstrated by user6445533 is not one of them. Here is it, done with closures:

const entries = (o) => ({
  entries: Object.entries(o),
  next() {
    return this.entries.length
      ? { value: this.entries.shift() }
      : { done: true };  
  },
  [Symbol.iterator]() {
    return this;
  }
});

const m = new Map(entries({foo: 1, bar: 2}));

console.log(Array.from(m)); // [["foo", 1], ["bar", 2]]

So, maybe it's all syntactic sugar in the end?

  • 1
    Unless I'm mistaken, isn't using `Symbol.iterator` the same as creating a generator? – Kevin Ghadyani Jun 22 '23 at 21:11
  • 1
    @KevinGhadyani By using `Symbol.iterator` your object is conforming to the [iterable protocol](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_generators#iterables), which generators must follow too. My point would be, a generator is just a closure conforming to a protocol, and the keywords `function *` and `yield` are only innovations in terms of syntax – nascente_diskreta Jun 25 '23 at 13:52