getify / Functional-Light-JS

Pragmatic, balanced FP in JavaScript. @FLJSBook on twitter.
http://FLJSBook.com
Other
16.6k stars 1.96k forks source link

Chapter 8 - inaccurate information about PTC in JS engines #204

Closed SpencerWhitehead7 closed 2 years ago

SpencerWhitehead7 commented 2 years ago

Chapter 8 says that as of ES6, JS engines implement PTC

JavaScript has never required (nor forbidden) tail calls, until ES6. ES6 mandates recognition of tail calls, of a specific form referred to as Proper Tail Calls (PTC), and the guarantee that code in PTC form will run without unbounded stack memory growth. Practically speaking, this means we should not get RangeErrors thrown if we adhere to PTC.

However, the most recent versions of Chrome, Safari, and Firefox all throw RangeErrors (with slightly different messages) if you run the PTC-ed version of sum given as sample code in the chapter with a large enough input

function sum(num1,num2,...nums) {
    num1 = num1 + num2;
    if (nums.length == 0) return num1;
    return sum( num1, ...nums );
}

sum(...Array(10000).fill(0).map((x, i) => i));
// Chrome "Uncaught RangeError: Maximum call stack size exceeded"
// Safari "RangeError: Maximum call stack size exceeded."
// Firefox "Uncaught InternalError: too much recursion" if you click the message's "learn more" link it sends you here https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Too_much_recursion , which indicates the error is a form of rangeError

This stackoverflow answer indicates that although it's part of the spec, browsers deadlocked over actually implementing it and it hasn't been adopted https://stackoverflow.com/questions/54719548/tail-call-optimization-implementation-in-javascript-engines (and Node can't do it because V8 doesn't do it). caniuse has a special footnote on their es6 support chart to indicate that most browsers haven't implemented it too https://caniuse.com/?search=es6 (their chart says safari does support it, but it didn't work on my computer's Safari 15.2, so I'm not sure what to make of that).

I think all the information about what PTC, PTO, trampolines, etc are and how they work is fascinating and useful, but the chapter should be updated to indicate that PTC doesn't actually work in existing JS engines.

getify commented 2 years ago

Safari supports PTC. You have to use strict mode on your code to enable it.

Quoting the text:

First, PTC in JavaScript requires strict mode. You should already be using strict mode, but if you aren't, this is yet another reason you should already be using strict mode. Did I mention, yet, you should already be using strict mode!?

...

"use strict";

function sum(result,num1,...nums) { result = result + num1; if (nums.length == 0) return result; return sum( result, ...nums ); }

Now our sum(..) is in PTC form! Yay!

If you look at every PTC example in the text, they all deliberately include strict mode. That's not an accident, it's intentional and required.

inaccurate information about PTC

The text of the chapter is not "inaccurate". The spec requires PTC. The non-conforming engines are in open defiance of the spec, and it remains an open question if they will ever adhere. They also may eventually support tail calls with explicit new syntax. Or they might eventually remove it from the spec.

I have no idea which future will play out. But the engines openly defying the spec and not resolving the conflict is their fault and a stain on their credibility, IMO.

I don't intend to keep updating my book text to reflect their shifting stances and the political wranglings that keep it an open conflict. I omitted all these details from the text because a printed book that may sit on a shelf for 20 years is not the place for such transitory information, if possible to avoid.

Once the engines resolve this squabble, I'll update the text accordingly.

BTW, again quoting the text:

One advantage with trampolines is you aren't limited to environments that support PTC

The primary reason the text covers trampolines is to show a practical interim approach that embraces the power of recursion while side-stepping the lack of PTC.

SpencerWhitehead7 commented 2 years ago

Thank you for explaining about Safari support. I tried it in a strict mode script, rather than the console, and it behaves as explained in the text.

It's your book and you're clearly aware of the issue and have no intention of changing it (and I understand how constant small updates don't make sense for printed texts), so I'll close the issue, but I think a simple addition to that first paragraph to inform readers that most engines don't adhere to the spec and a suggestion to check current support would be future-proof without having to track every little twist and turn in the process (until the issue is resolved for real) and helpful for readers. They have been out of compliance for years with no progress I can find.

Practically speaking, this means we should not get RangeErrors thrown if we adhere to PTC. is literally true, and, reading it more closely, it sidesteps the whole implementation issue without saying anything false quite neatly from, like, an English prose perspective. But for whatever it's worth, I would like to try and explain why I think most readers could still interpret it in a way that conveys inaccurate information. The most literal reading is "Since it's in the spec, if you adhere to PTC you should not get RangeErrors" (accurate), but another possible reading is "In practice, if you adhere to PTC, you won't get RangeErrors" (inaccurate). If there was no implementation issue, the two readings would be interchangeable, so I think most readers who aren't already aware of the issue would default to the second reading, and not pick up on the first reading's unspoken parenthetical "(but in practice you will anyway since most engines haven't implemented it)." If you don't know about the implementation issue and you make the second interpretation, it would be pretty confusing to see your strict mode, PTC-ed code causing a RangeError and you'd be left to go digging for the underlying cause of the problem on your own.

One of the great things about the book is the focus on practicality and the way it meets JS where it's at. As I said, the trampoline section is interesting and useful, and more clarity about the lack of actually existing, as opposed to theoretical, PTC in JS would make it clearer why that particular technique is especially useful for writing JS in existing environments.

getify commented 2 years ago

I appreciate your thoughts.

In retrospect, had I known back in 2017 at original writing that this would still be unresolved almost 5 years later, I probably would have included a note such as you describe.

I indeed debated a mention of the issue with my editor at the time, but elected not to bring up a topic that is actually (now) so convoluted as to take quite a bit of text to properly explain. I eventually decided that saying something (speculative or just snarky) about it would have aged more poorly than not saying anything at all. It's a question of whether saying too little about something is worse than saying nothing at all.

Paradoxically, the older the text gets past its publication date, the less justified it feels to post changes of substantive messaging difference, because then you have the problem where people reading the book get a different experience from those reading the text on this site.

So it's a tough judgment call. I wish the engines and TC39 would get the blame here and get their act together one way or the other. I unwittingly was stuck smack in the middle of the political debates on this topic some years back, so I'm still upset about it all and I resent that we even have to still juggle this.

Again, appreciate your thoughts. It's relevant, and even if for no other benefit I'm glad this discussion is here for posterity sake.