6 ms·
I don’t think the example is particularly good. TCO is essential in functional programming, where recursion shines, but the example given is an impure function
by odyssey7 3y ago
I don’t think the example is particularly good. TCO is essential in functional programming, where recursion shines, but the example given is an impure function which mutates its input parameter by calling Array.prototype.push. Basically, this isn’t a demonstration of the paradigm for which TCO is required.
We’ll never know how big of a role TCO could have played in JavaScript over the past several years, because it hasn’t been available outside of some narrow contexts.
- MrJohz 3y agoI agree that TCO is essential in functional languages where loops and mutability are disallowed or at least heavily discouraged, but Javascript is not one of those languages. In Javascript, both mutation and and loops are very simple, and as I think the article demonstrates, often the clearest ways to express an idea. Where recursion is clearer (for example, when dealing with recursively nested data structures), the clearest expression of a function usually isn't tail recursive - that often requires rewriting the function to put more state into the function arguments. This means that tail recursion rarely matches with patterns used regularly in Javascript. That's not to say it's useless - if you want to keep to a particular functional style, or if you're compiling to Javascript from a language that emphasises recursion, then it's a useful tool to have. But in the former case, you're generally going to struggle with browser engines not being optimised for this style, and in the latest case, you're probably better off targeting WASM directly.
- odyssey7 3y agoI agree with everything you wrote. There’s an element in this that gets at why V8’s omission of TCO feels to me like suppression of functional programming. JavaScript is an incredible widely used multi-paradigm language, to the point that I would recommend for universities to replace Python and Java with JavaScript in their curriculums. It’s just so practical for building software systems, has amazing tooling, connects you to a rich community, and allows you to cover so much academic ground. JavaScript has been a rocket fuel for promoting a vibrant, diverse software engineering ecosystem. Although you can choose to write a loop and do things imperatively in JavaScript, you don’t have to. The ECMAScript spec is inclusive toward the functional programming paradigm by requiring that engines provide for proper tail calls. Functional programmers should be allowed to have their cake and eat it too! But TCO isn’t implemented in V8, and the workaround is to just switch from functional to imperative programming.
- MrJohz 3y agoI don't think it's the lack of TCO that's the thing holding JS back from getting the next great functional language. There's no structural pattern matching, there's no immutable data structures, there's nothing in the way of convenient syntax sugar like currying. You can mimic all of that, but it's painful, it's not something that Javascript is that great at. Which is fair enough, it doesn't need to be perfect at everything, that's why there's different languages. If you want functional programming in the browser, try using Elm or ReasonML. Both of those are fairly easy to use, I think there are even online playgrounds for both to get started.
- odyssey7 3y agoI think it’s not as widely recognized as it could be just how forward-looking ECMAScript was in introducing language updates in the mid 2010s. For instance, arrow functions make it natural to work with partial application, a currying-adjacent technique. const times = a => b => a * b const double = times(2) const twiceFour = double(4) As a language feature, a function to curry a given function is different from proper tail calls in that it can be implemented as a JavaScript function rather than needing to be built into the JavaScript engine. Similarly, Haskell’s own curry function is imported from its standard prelude. Leaving this detail up to language users allows a diversity of approaches and vibrancy in the community. Lodash has had an implementation for a while: https://github.com/lodash/lodash/blob/0843bd46ef805dd03c0c8d804630804f3ba0ca3c/lodash.js#L10200 https://github.com/lodash/lodash/blob/0843bd46ef805dd03c0c8d.... (Edit, found better example.) Structural pattern matching can be very elegant and it would be nice if ECMAScript offered it. Object destructuring has been added to the language, and that feature could be seen as a stepping stone for adding structural pattern matching in a later version. Proper tail calls however are more fundamental to the paradigm of functional programming, since they are necessary for iterative algorithms to work as well functionally as they do imperatively.
- deleted 3y ago[deleted]
- MrJohz 3y ago
- o11c 3y agoThe real problem, of course, is that in the cases where compiler-based TCO is possible, the user can trivially refactor the function to use a loop.
- tonyg 3y agoThis is not true. The compiler can trivially turn this into a jump, but the developer doesn't have enough context to construct any kind of loop: function ap(f, v) { return f(v); }