<story><title>Bun, JavaScript, and TCO</title><url>https://www.onsclom.net/posts/javascript-tco</url></story><parent_chain><item><author>tomashubelbauer</author><text>I find it surprising that the JS specification also includes optimizations an engine should implement. I&amp;#x27;m not sure how difficult TCO is to implement, but I just checked if QuickJS supports it or not and it seems to be one of the few ES2020 omissions the author chose to make: &lt;a href=&quot;https:&amp;#x2F;&amp;#x2F;bellard.org&amp;#x2F;quickjs&amp;#x2F;quickjs.pdf&quot; rel=&quot;nofollow&quot;&gt;https:&amp;#x2F;&amp;#x2F;bellard.org&amp;#x2F;quickjs&amp;#x2F;quickjs.pdf&lt;/a&gt; (3.1.1). This tells me it might be non-trivial to implement which if true makes the decision to make it a part of the specification all the more surprising, because that means the specification restricts the types of trade-offs implementers can make when attempting to achieve full compatibility. In other words, until today I was under the impression you could make a fully ES compatible engine, but choose to make it slow for implementation ease. Looks like the spec defines the floor for how (non)-performant the engine implementation can be in some cases. Is this common in other languages&amp;#x27; specs?&lt;p&gt;Edit: Oh, seeing the sibling comment, are TCO and &amp;quot;tail calls&amp;quot; different things? If so, I remain unclear on the status of TCO support in QuickJS.</text></item><item><author>pjmlp</author><text>Regardless, it has been approved as part of JavaScript specification and Chrome folks refuse for political reasons to implement it, which given the market domination everyone has helped them achieve is a bummer.</text></item><item><author>MrJohz</author><text>I think this article actually does a really good job of explaining why TCO hasn&amp;#x27;t really been an important part of the JS ecosystem.&lt;p&gt;There&amp;#x27;s a lot of situations where recursive algorithms are really neat and clear. I don&amp;#x27;t know if this is the best example, but it shows the benefit of being able to split logic into a base case and a recurrence case.&lt;p&gt;But, in my experience, an algorithm that elegantly fits a recursive relationship is rarely one that naturally fits the tail-call paradigm. Often part of the benefit of recursion is that you can store state in the stack - the very thing you need to avoid to use TCO.&lt;p&gt;This means you often need to put in effort to create a tail recursive algorithm. But that often ends up looking a lot like the imperative case anyway - an accumulator outside the loop that you either mutate manually, or update in a tail call. And in my experience, the mutating, imperative version is usually then the easier to read and write (assuming you can keep mutations to a given scope, and not have that state leak all over the place). (In fairness, this might be more familiarity, though.)&lt;p&gt;In the light of this, what is the advantage of TCO? In functional languages without mutation, it&amp;#x27;s pretty important to allow for functions to act on arbitrarily-sized inputs without constantly growing the stack. But if we have mutation, it&amp;#x27;s really just a different way of writing the same code. And if that different way is generally less clear and almost always less performant, it probably isn&amp;#x27;t a very useful choice.&lt;p&gt;Which is why I think TCO hasn&amp;#x27;t really caught on in the other JS engines. It&amp;#x27;s a cool idea, and there&amp;#x27;s definitely a handful of cases where it&amp;#x27;s the useful way to go, but usually you&amp;#x27;ll be better served by writing things in the more traditional JS way.</text></item></parent_chain><comment><author>mananaysiempre</author><text>There’s a bit of a naming confusion around tail calls, but in any case “proper tail calls”, let’s call them that, are not precisely an optimization: they are a &lt;i&gt;guarantee&lt;/i&gt; that any number of recursive calls of a certain kind will result in constant and not linear memory consumption. This permits some kinds of programming that can otherwise be quite awkward. If your program takes advantage of them (as e.g. often happens in Scheme), having tail calls is not an optimization issue, it’s an implementation correctness issue.&lt;p&gt;Now tail calls are quite annoying to implement &lt;i&gt;in C&lt;/i&gt; on top of a compiler that doesn’t (and in fact I don’t think any mainstream platform has a C ABI that would allow a C compiler to natively support them between functions of arbitrary types). That has always been a (solvable) issue for Scheme-to-C translators, and it was probably a consideration for QuickJS. Chrome’s V8, though, is so far away from interpreting things in C or even translating them to C that I expect that any difficulties the developers have are of a completely different nature.&lt;p&gt;(As an example, LuaJIT does support tail calls but kind of sucks at inferring hot loops written using them, so has really impressively draconian limits on how many you can have before the JIT aborts. They otherwise work, though, as is required for a valid Lua implementation.)</text></comment>
<story><title>Bun, JavaScript, and TCO</title><url>https://www.onsclom.net/posts/javascript-tco</url></story><parent_chain><item><author>tomashubelbauer</author><text>I find it surprising that the JS specification also includes optimizations an engine should implement. I&amp;#x27;m not sure how difficult TCO is to implement, but I just checked if QuickJS supports it or not and it seems to be one of the few ES2020 omissions the author chose to make: &lt;a href=&quot;https:&amp;#x2F;&amp;#x2F;bellard.org&amp;#x2F;quickjs&amp;#x2F;quickjs.pdf&quot; rel=&quot;nofollow&quot;&gt;https:&amp;#x2F;&amp;#x2F;bellard.org&amp;#x2F;quickjs&amp;#x2F;quickjs.pdf&lt;/a&gt; (3.1.1). This tells me it might be non-trivial to implement which if true makes the decision to make it a part of the specification all the more surprising, because that means the specification restricts the types of trade-offs implementers can make when attempting to achieve full compatibility. In other words, until today I was under the impression you could make a fully ES compatible engine, but choose to make it slow for implementation ease. Looks like the spec defines the floor for how (non)-performant the engine implementation can be in some cases. Is this common in other languages&amp;#x27; specs?&lt;p&gt;Edit: Oh, seeing the sibling comment, are TCO and &amp;quot;tail calls&amp;quot; different things? If so, I remain unclear on the status of TCO support in QuickJS.</text></item><item><author>pjmlp</author><text>Regardless, it has been approved as part of JavaScript specification and Chrome folks refuse for political reasons to implement it, which given the market domination everyone has helped them achieve is a bummer.</text></item><item><author>MrJohz</author><text>I think this article actually does a really good job of explaining why TCO hasn&amp;#x27;t really been an important part of the JS ecosystem.&lt;p&gt;There&amp;#x27;s a lot of situations where recursive algorithms are really neat and clear. I don&amp;#x27;t know if this is the best example, but it shows the benefit of being able to split logic into a base case and a recurrence case.&lt;p&gt;But, in my experience, an algorithm that elegantly fits a recursive relationship is rarely one that naturally fits the tail-call paradigm. Often part of the benefit of recursion is that you can store state in the stack - the very thing you need to avoid to use TCO.&lt;p&gt;This means you often need to put in effort to create a tail recursive algorithm. But that often ends up looking a lot like the imperative case anyway - an accumulator outside the loop that you either mutate manually, or update in a tail call. And in my experience, the mutating, imperative version is usually then the easier to read and write (assuming you can keep mutations to a given scope, and not have that state leak all over the place). (In fairness, this might be more familiarity, though.)&lt;p&gt;In the light of this, what is the advantage of TCO? In functional languages without mutation, it&amp;#x27;s pretty important to allow for functions to act on arbitrarily-sized inputs without constantly growing the stack. But if we have mutation, it&amp;#x27;s really just a different way of writing the same code. And if that different way is generally less clear and almost always less performant, it probably isn&amp;#x27;t a very useful choice.&lt;p&gt;Which is why I think TCO hasn&amp;#x27;t really caught on in the other JS engines. It&amp;#x27;s a cool idea, and there&amp;#x27;s definitely a handful of cases where it&amp;#x27;s the useful way to go, but usually you&amp;#x27;ll be better served by writing things in the more traditional JS way.</text></item></parent_chain><comment><author>basil-rash</author><text>Scheme (what JavaScript was based on) also requires tail call optimizations as part of its spec.</text></comment>