Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why recursive generator function doesn't work in ES2015?

I am trying to understand generators in ES2015 and have created a recursive factorial function with it. But it doesn't work. I have referred already existing question like this on the topic, but it doesn't help.

function* fact (n) {
   if (n < 2) {
     yield 1;
   } else {
     yield* (n * fact(n-1));
   }
}

let b = fact(5);
console.log(b.next()); 

Can anyone find any obvious issues I am missing here? I am using this in JSFiddle with JavaScript-1.7 here

like image 656
Aditya Singh Avatar asked May 12 '16 06:05

Aditya Singh


Video Answer


2 Answers

Can anyone find any obvious issues I am missing here?

fact returns an iterator, yet you are trying to multiple it with a number: n * fact(n-1). That cannot work!

Because fact returns an iterator, but you also want to multiply the last value of the iterator with n (i.e. it is not tail-recursive), you cannot simply yield* it either.
You need to explicitly iterate over the result from the internal call, reemit the value and remember the last value so that you can multiple with it:

function* fact (n) {
   if (n < 2) {
     yield 1;
   } else {
     let last;
     for(last of fact(n-1)) {
       yield last;
     }
     yield n * last;
   }
}

Array.from(fact(5)); // [1, 2, 6, 24, 120]

If you change the function to be tail-recursive, it would be a bit shorter (and nicer), but the result would also be different (since we perform the operations in different order, at least in this implementation):

function* fact (n, acc=1) {
   yield acc
   if (n > 1) {
     yield* fact(n-1, acc * n);
   }
}
Array.from(fact(5)); // [1, 5, 20, 60, 120]

Personally I would just write a non-recursive version:

function* fact (n) {
  let result = 1;
  let i = 0;
  while (i < n) {
    yield result = result * ++i;
  }
}
Array.from(fact(5)); // [1, 2, 6, 24, 120]
like image 119
Felix Kling Avatar answered Nov 15 '22 14:11

Felix Kling


Just to add another tail-call recursive solution that returns the desired result, but takes three arguments (a slight variation from Felix Kling's second example):

function *factorial(n, add=1, cnt=1) {
  yield add;
  if (cnt < n) {
    cnt++;
    yield* factorial(n, add * cnt, cnt);
  }
}

Array.from(factorial(5));
// Array [ 1, 2, 6, 24, 120 ]
like image 38
nils Avatar answered Nov 15 '22 14:11

nils