Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

About Tail Call Optimization #340

Open
Bamieh opened this issue Sep 19, 2016 · 3 comments
Open

About Tail Call Optimization #340

Bamieh opened this issue Sep 19, 2016 · 3 comments

Comments

@Bamieh
Copy link

Bamieh commented Sep 19, 2016

In your book, you have the following line:

The tail call does not require access to variables in the current stack frame (meaning the function is not a closure).

I was discussing the issue on stackoverflow, and a few pointed out that this is not in the spec, and closures do not interfere with TCO since free variables of closures are stored on the heap. So a closure can live longer than its surrounding scope, where it was declared.

Can you explain why closures would prevent TCO, is there any reason?

I looked in the spec, and indeed i was unable to find any mention to closures in regards to the TCO topic. section (12.4 i believe).

@nzakas
Copy link
Owner

nzakas commented Sep 19, 2016

Can you explain why closures would prevent TCO, is there any reason?

As far as I know, closures prevent TCO because the enclosing the stack frame is reused/overwritten for TCO. Closures rely on the enclosing stack frame for variable lookup, which means it can't safely be destroyed.

That said, none of this matters as it looks like implicit TCO will likely be removed in favor of explicit TCO.

@getify
Copy link
Contributor

getify commented Sep 19, 2016

none of this matters as it looks like implicit TCO will likely be removed in favor of explicit TCO.

I think it definitely matters, because if closures prevent any sort of tail call technique, regardless of whether it's implicit or explicit, then this is definitely something that developers will have to be aware of. It's actually incredibly likely that functions people expect to be PTCd will have a closure over an outer scope, so this could be a huge caveat that would kill the usefulness of most PTC.

closures prevent TCO because the enclosing the stack frame is reused/overwritten for TCO

I don't think closure would be an issue in the general PTC case. Lemme illustrate:

var bar = (function IIFE(){
   function foo() { return 42; }

   return function bar(x) {
      if (x < foo()) return x;
      return bar( x / 2 );
   }
})();

bar( 608 );   // 38

Here, bar() definitely closes over foo, but I don't think that will prevent the PTC, because the closure is outside the scope of bar(). Each time the stack frame for bar() is recreated/reused, the closure it has over the enclosing scope of the IIFE() function remains intact and unchanged. Doesn't seem like there's any reason at all that the second call of bar() can't adopt/share the same scope closure that the first bar() call had. The outer scope doesn't ever change or go away while that recursion is going.

And now a counter example that definitely would be problematic for PTC and closure:

function bar(x) {
   if (x == 304) setTimeout( function inner() { console.log(x); }, 1000 );
   if (x < 42) return x;
   return bar( x / 2 );
}

bar( 608 );   // 38 

// 1 second later:
// 304

Here, the closure that inner() gets over the scope of bar() to remember the x, that definitely does seem like it would prevent that particular stack frame of bar() from being discarded.

The good news is, I suspect that closure inside of a recursive function is exceedingly rare, whereas closure outside of its own scope is probably pretty common.

@Bamieh
Copy link
Author

Bamieh commented Sep 24, 2016

@getify I agree that it is indeed rare to write inner closures like that in this context, explicit tail calls might make it clearer that tail calls are actually working, debugging implicit tail calls might not be the average programmer's thing i guess.

@nzakas so your statement holds true then, although outer closures should work fine since they are not affected at all, closures inside the recursive call prevent stack frame reseting in tail calls since closures rely on the enclosing stack frame for variable lookup, which means it can't safely be destroyed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants