4 ms·
Not to be a dog in the fight but… How is: const count = (amount: number) => (amount > 0 ? [...count(amount - 1), amount] : []); Succinct and better? It’s
by reactordev 3y ago
Not to be a dog in the fight but…
How is:
const count = (amount: number) => (amount > 0 ? [...count(amount - 1), amount] : []);
Succinct and better? It’s fewer lines. It uses recursion. But it’s obtuse and illegible syntax diarrhea that takes more time to grok.
The for loop example above, while simple, is easily understood. A junior engineer could fix it. Is it faster? No. Is it easier to understand? Yes. I like the final example as well.
Look, I’m all for being clever when cleverness is needed but some of you JS/TS folks take it to the extreme. I want to be able to read my codebase, not perform archaeological studies. I have the same issue with Rust syntax as well in places.
I will praise the article on this though, optimizations do matter and having something execute orders faster will improve your overall experience. It doesn’t have to be cuneiform. If you must be clever, wrap it in an abstraction that is well documented because you don’t live forever and neither will your code, make it easy(ier) on the next person. While you can nest a recursive function in a tertiary statement coro, don’t. Fire is cool but also burns.
Btw, bun is blazingly fast. I look forward to more.
- mhink 3y ago> Look, I’m all for being clever when cleverness is needed but some of you JS/TS folks take it to the extreme. To be fair, this is very much a toy example meant to demonstrate tail recursion- although I do agree it's not a great example considering you need to understand the evaluation order of ternaries in order to understand why one version is tail-recursive and the other isn't. That being said: generally speaking, one might argue that using recursion at all is "too clever" and algorithms should just stick to iteration altogether. ¯\_(ツ)_/¯ > While you can nest a recursive function in a tertiary statement coro, don’t. Man, why do you gotta call me out like this? :D
- akoboldfrying 3y ago>algorithms should just stick to iteration altogether Well, if your algorithm performs 2 or more recursive calls per function invocation (like, say, quicksort), at least one of them needs to be an actual recursive call that uses additional stack space, since TCO is only able to mitigate a single recursive call per function invocation. (You could simulate your own stack and use iteration... but the only reason to do so in practice would be to escape some artificial restriction on native stack size.)
- reactordev 3y ago>”Man, why do you gotta call me out like this?” Because you lose the stack trace most of the time. Only God and you know where the Uncaught PromiseRejection happened. It’s just really hard to debug when things go south. This drives me bonkers with the AWS sdk.
- satvikpendem 3y agoIf one is well-versed in functional programming, they would understand the above function just fine, plus it comes with all the benefits of functional versus imperative styles, which I will not repeat here as there are lots of articles on this. Perhaps JS devs should spend more time in functional codebases rather than imperative ones and they'll also get used to such code, and I for one am glad to see TCO making this style more efficient compared to traditional for-loops.
- djur 3y agoThis style isn't efficient in JS, though, because it produces a ton of garbage to collect. TCO doesn't really make it more efficient, just possible.
- deleted 3y ago[deleted]
- deleted 3y ago[deleted]
- cowsandmilk 3y ago> Perhaps JS devs should spend more time in functional codebases rather than imperative ones the pure functional version takes 700x longer to run than the imperative version; maybe the wins of functional programming just aren't there in JS engines today.
- reactordev 3y agoIf one was well-versed in memory management and how the stack would blow up in production one would not have to repeat them here. Also, if you have to justify cleverness, it’s not clever. Don’t defend the style by grandstanding on “functional is better” or “you don’t know func bro”, how about ask why I feel the imperative style is more legible? Why would this not be the best way to write this? Where can I write this way? Or even “I find this style to be legible to me, but I’m curious about why you think it’s not”.
- kannanvijayan 3y agoI’m surprised no one has brought up the fact that this code has quadratic time complexity when the underlying algorithm could be linear. And the fact that it’s allocation behaviour is horrible, leaving behind ‘count-1’ garbage objects, count-squared garbage memory, and forcing the GC to chase after the algorithm. I can accept this example as a contrived scenario to talk about TCO… but even in the presence of TCO one would not want to implement the function this way.
- djur 3y agoThe article doesn't explain why (and it should) but it does mention that this version is very slow and offers a version that doesn't have the same problems.
- reactordev 3y agoI wasn’t even going to dive into the allocations and garbage this style brings but it’s orders of magnitude slower, with more garbage created, disposed of, than a simple if statement. I’m glad you opened the can of worms. There are times when this doesn’t matter. Reading a config json, or performing your startup bootstrap, but if this was executed during a request or during a frame then you’re going to have a bad time.
- nsonha 3y agolooks linear to me? Takes n calls, each is O(1). > the fact that it’s allocation behaviour is horrible, leaving behind ‘count-1’ garbage objects Topic is TCO, which is kinda about that.
- eddd-ddde 3y agoThe array spreads mean that each step has to do linear work to create the next array
- mitt_romney_12 3y agoIf you replace the ternary with an if then it's easy to understand for anyone who knows what ... means (which imo every JS developer should know) const count = (amount: number) => { if (amount > 0) { return [...count(amount - 1), amount] } else { return [] }; }
- reactordev 3y agoYou missed the point entirely I’m afraid. It’s not about replacing the if with …, it’s about complexity.
- femiagbabiaka 3y agoThe ubiquitousness of c-style makes it more familiar. But that doesn't make it better syntactically. And of course what's faster or more memory efficient is an implementation detail ultimately, but moving away from the imperative style opens up the opportunity for new and interesting -- and potentially more grokkable, ultimately -- patterns. Rust is actually a great example of this, with things like match and pattern matching. Don't be such a language grinch.
- reactordev 3y agoTalk to me about lifetimes of Boxed RC Mutex then… because last time I checked, I had to do some insane amount of <T> to get a concurrent collection synchronized. I’m all for functional. Just don’t over use it in places where a simple function would suffice and try not to use it when dealing with recursive allocations of temporary arrays. Rust has come a long way but I feel like some of their style choices were to feel superior rather than make something functional. Go is a great example of simple, functional, but not quite as robust - as Rust.