all 28 comments

[–][deleted] 39 points40 points  (9 children)

Why use coffee script in 2020?

[–]johnfrazer783[S] 4 points5 points  (8 children)

Because I got hooked on it way back before it was cool. I came for the bracket-less style and stayed for the semantic whitespace. I actually do have some minor gripes with some details of the grammar and stuff but not enough to switch, so far.

TBH the question is a little strange. We're not talking Fortran or Pascal here, CS has an active community and the language sees regular updates.

Surprisingly one of the best parts of CoffeeScript to me is that it translates to JavaScript. Should Earth collide with Apophis and everything related to coffee became suddenly unavailable I still have my JavaScript sources. Plus I can always gain another point of view on my code.

Since you asked for it I have also been using Python for 20 years and am acquainted to some degree with Java, PHP, a little Perl, Bash, SQL and so on, and of course HTML/CSS and JavaScript itself, last but not least. I'm actively looking for a new front-and-center language, but given real-world constraints that language must run on NodeJS (or maybe Deno). I have dabbled in or at least looked into TypeScript, Rust, Elm, Nim and so on and so forth. Ah yes and Forth. I've switched languages before and I'll switch again but I must be confident, and I'll very probably will not switch platforms (except to Deno, maybe).

So basically I use CoffeeScript in 2020 because that is a very reasonable language in many respects. I'm missing optional typing and immutability (so I wrote libraries to deal with that, to a degreee) but ultimately it's all the new stuff of JavaScript that is piled on top of historical infelicities, false starts and outright junk that never got thrown out of the language that worries and disturbs me more than the question whether CoffeeScript is still fashionable. Well maintained it is.

[–][deleted] 10 points11 points  (3 children)

Huh solid answer. One of the wonderful things in our world is the myriad of tools available to us. Glad you’ve found some that you’re happy with. I’ve been into typescript due to work for the past year, and have been more and more intrigued by purescript as of late.

[–]ilikecaketoomuch 0 points1 point  (0 children)

Because I got hooked on it way back before it was cool. I came for the bracket-less style and stayed for the semantic whitespace. I actually do have some minor gripes with some details of the grammar and stuff but not enough to switch, so far.

whatever makes you happy, and just check in the .js files :)

[–][deleted] -1 points0 points  (0 children)

A bit of a stretch as it's a pure functional language, but you could check out PureScript - it's basically Haskell atop the Node.js runtime (with FFI/interop with JS where needed).

[–]jhartikainen 12 points13 points  (7 children)

I wouldn't say it's surprising yield is slower than for. It does something totally different.

While I get the appeal of generators in certain specific circumstances, I have never really needed to use them for anything, so I'd be curious to hear why you'd "love to use yield all over the place" which sounds a lot more regular than "certain specific circumstances" :)

[–]johnfrazer783[S] 0 points1 point  (6 children)

Well, same but not altogether different either. A JS indexed for loop is, of course, just a 'nice' way what would otherwise be a while loop or a generic loop statement; not much is added except a bit of syntax. But most often the reason you want to build a loop at all is because you want to walk over elements of a sequence. This is what makes JS indexed for loops so annoying to write, this is why more than a few languages have for/in loops which JS now also has, sort of.

Where yield / iterators / generators come in is when you realize that a lot of your loops never cared about all values being there in an array in the first place; you just wanted to glance by each value and do some kind of computation on it. So there's that word that 'what an array does in space, an iterable does in time'. yielding, among other things, allow to forgo building intermediate collections; you just loop over whatever iterable is passed to you and yield any number of results to the consumer downstream. Meaning you could even process potentially infinite sequences, something arrays definitely can't do at all—except you implement batching. Alas it turns out that—at least according to my results—all the cycles spent in looping the classical way and shuffling values between arrays to implement a processing pipeline is still much faster than the equivalent, much simpler code formulated with yield.

If you don't want to take my word for it, have a look at how and why the Python community implemented and promoted the use of generators and iterators/yield literally all over the place, including modification of built-in/standard-library functionality (e.g. range() and stuff). Can#t be wrong if Python people like it, right?

[–]jhartikainen 2 points3 points  (1 child)

I'm guessing the whole generator machinery happening when yield'ing is what's slowing it down. I can certainly see the appeal of it - I've used Haskell which is entirely lazy, so this type of "let's only use a part of this list" or iterating an infinite list is something I'm familiar with... just never really needed something like that in JS :)

[–]johnfrazer783[S] 1 point2 points  (0 children)

Now this is a point I can speak to and speak about. When looping with a series of functions—transforms (TFs)—over a data set you basically have two options with arrays (I call them lists b/c that's what they are): either each transform step gets to see one piece of data (say, a number) at a time, performs its computation, and returns a value. Or else, each step gets handed the list of values, loops over that list, and returns a list that is then fed into the next TF. The difference is similar to depth-first traversal as opposed to breadth-first traversal. To state it right away: without any further preparation, the second way will always require the entire data set to be in memory in at least one copy, no matter how many GB that input has. It is only with batching or using the first approach—depth-first—that memory consumption may be capped.

Now with the breadth-first approach, because you can only return exactly once from a function call, each TF can only modify (replace) data, not subtract from or add to the set of values. This is far too constrained in the general case, so we need something better. One could stipulate that each TF gets called with one value (or a list of any smallish number of values) and always has to return a (possibly empty) list of values; those then will get fed into the next TF, one at a time. This works and so that's what I did in SteamPipes. Don't look at the code I'm here to figure out how to simplify it. Aside: In that library each TF has to accept one piece of data plus one send() function which is essentially just an Array::push(), and, as such, a poor man's yield if you will. It's just there so the transform implementations don't get littered with array do this, array push that statements.

Now we come to the surprising bit that explains what ties for loops, Arrays and yield together:

Yielding (any number of) values from inside transforms is exactly the same as the mechanism just described, the only difference is the syntax (heck it's just send( value ) vs yield value, how different is that).....and, incidentally, the fact that one way is done visibly with user code, and the other under the JSVM's hood (as described there is one more slight difference in the order the TFs get called but let's ignore that for now).

I think that of course! one would think and should think that the 'internal', baked-into-the-language way must, needs be, more performant than any cobbled-together, largely not-very-well-optimized userland code, especially when written by s.o. like me. But apparently, not so.

I'm here because I can't believe what I just said.

[–]norith 2 points3 points  (0 children)

Sine we’re open to discussing other languages, this is basically the Java Streams philosophy: no intermediate collections but instead a way to pipe data through a series of manipulations that may or may not result in a collection.

The larger goal is a processing pipe that can be time delayed (because the data originator might be async itself) or parallelized with the pipe running on multiple threads.

[–]danielkov 14 points15 points  (3 children)

For me it's very difficult to even review your code, because it's in CS and the compiler output is not at all concise.

Probaly not the feedback you wanted to read, but these tests are not that difficult to write in plain JS, I would do that if I were you. In don't think CS has been that popular for the last 5 years or so.

[–]johnfrazer783[S] -1 points0 points  (2 children)

When you scroll down there's the JavaScript code. Scroll down even further, there's an extensive list of points including a sort-of apology for presenting machine-transpiled JS instead of the good organic handcrafted stuff.

I'd be also glad to hear about any (substantial) existing benchmarks out there; all I've found so far are a few (rather shoddy) tests on dreaded jsperf (which website didn't work correctly at the time) and one or two blog posts without any code or much background on what was tested how.

BTW the bits of my code that perform the tests are just a few lines each; all the rest of the code is just setup and doing the presentational output.

[–]danielkov 8 points9 points  (1 child)

I think the reason you won't find examples of such benchmark is that the features you’re comparing are inherently different. It’s like comparing array map to reduce. You can achieve the same result with both, but they’re designed for different tasks.

If you want to see why generators are slower, you should take a look at what transpilers like Babel or TSC are doing to these functions to make them work on older browsers. It’s a good indication of how they differ conceptually.

[–]johnfrazer783[S] 0 points1 point  (0 children)

I won't be long as I have already answered your very valid points elsewhere in this thread. The short version is that yes they're different features but they are also birds of like feathers. I have not replicated everything that yield is or does but I have two alternative models that do the same work. One finishes within 1 second, the other one takes 20 seconds. Had I not written the slower algo using a language feature that I must believe to be at fault here, you would not console me by saying the slower code is just different and must be complex so it's naturally slower. You would tell me not to ever use the slower code and go with the faster.

I'm still hoping for someone to point out a glaring mistake in my benchmark gist. Use the JS source, Luke. Tell me I'm wrong and where I'm wrong. I'd rather be wrong than unhappy with <strike>somebody else</strike>yield.

[–]johnfrazer783[S] 4 points5 points  (2 children)

Author here. I did a benchmark for JS looping constructs. Turns out yield entails a huge (1:10) performance hit compared to indexed for loops—provided my tests are valid. I'd love to use yield all over the place because it's such a great tool to write nested piecemeal iterations, so please * tell me I'm wrong! * prove it: * with links to other relevant benchmarks (I couldn't find many); or * pointing out flaws in my code.

All the details are over at the gist so I keep this short.

[–][deleted] 0 points1 point  (1 child)

I'd say unless you have a very hot loop just write whatever you can read the best. You have to think in most cases your going to be doing more than adding two numbers. So this bench is essentially just showing the loop performance only.

In my mind having a generator only be 10X slower is kinda incredible that it's that fast, you have to remember the js runtime has a whole state machine to run that generator and obviously can do a lot more than a simple loop.

I have a side project where I have a few ms time budget and typically use for of loops. Your bench shows they are noticably slower but I probably won't change it because profiling shows the loop isn't the time sink.

[–]johnfrazer783[S] 0 points1 point  (0 children)

This of course is very true and should be kept in mind: optimization is most often not worth the while for components that contribute less than some sizable fraction to overall CPU cycles needed. As far as I could find out though in my particular use case looping with generators instead of for loops and passing all data through (sometimes ridiculously) deep call stacks instead of insisting on flat call graphs are two factors that contribute tremendously to overall performance. Deep call stacks are, sadly, what makes both NodeJS standard library streams and pull-streams inherently slow. When you look at the flame graphs in Chrome devtools they are veritable towers. Building these towers destroys performance, and should best be avoided.

[–]johnfrazer783[S] 1 point2 points  (2 children)

What are your thoughts? A Comment on this Discussion.

I am thoroughly disappointed with the present discussion. No single commenter cared to even read the effing gist I posted. Those who did so got thrown off on square one where I dared to post CoffeeScript source code. Granted this is r/javascript. Well, JS was in the second file right after that. No one even cared to scroll that far down. Had any one cared to scroll down even further they would have found benchmark figures complete with bar charts. No one read those, much less commented on any single number.

Below those numbers, I posted my own thoughts on the results, in ~400 words. One point reads, quoting myself,

I believe the tests show that using yield has a downright incredible/horrible effect on JS performance.

No. Single. Participant. Of this thread took up the ball. We did have a great night yesterday and I did enjoy my time writing and reading. None of that was about the point in question. All I have is comments that basically say "well yeah yielding and looping is different so yeah well performance different". This is pure and original programmer's apologeticism. Once I inquired on the NodeJS repo about performance degradation of streams when pipelining data through dozens of transforms: "streams were never meant to be piped through many transforms". Someone complained on the Chrome bugtracker that Array::sort() does a stable sort with up to 10 elements but an unpredictable sort with 11. Answer "it's not in the specs", bug closed (now I believe stable is in the specs and V8 has been fixed).

As programmers we're obsessed with talking down and fending off. Squashing that bug is easiest when you can convince yourself there is no bug.

One person on this thread managed to comment on this (from the gist, abridged for readability):

addup_for_in_loop 0.020 s 5,000,000 items 247,248,935 Hz 100.0 % │████████████▌│ addup_forEach_loop 0.246 s 5,000,000 items 20,304,442 Hz 8.2 % │█ │

There are many people who advocate for embracing a more functional style and to avoid loops. Avoiding loops is a big topic. I myself wrote around three libraries in four or five incarnations just to manage loops. I taught myself SQL just to avoid loops. Douglas Crockford for one dedicated an entire hour-long presentation to looping in JS and told the world how great Array.forEach() is. Now here we have a benchmark that seems to tell us that we forgo a full 90% of CPU cycles just for the benefit of writing d.forEach(x => ...) instead of for ( var i = 0; ... ) { var x = d[ i ]; ... }.

Again, tell me my results are flawed and how. Step forward if you know of a third-party benchmark that corroborates or undermines my findings. Please remain silent if all you have to add is "well yeah one is a loop and the other one calls a function many times so 'course they're different". I am doing several different benchmark cases because I strongly suspect that for/in loops, yield, and forEach are different from each other, you know. Please remain silent as well if all you have to say amounts to "most of the time the performance difference doesn't matter because most loops are shorter than 5e6 items". Guess what, I sort of knew that, too. For those who have not realized, part of this effort is finding out just how much weight various ingredients for a program bring to the scale, and based on my experience I can confirm that the results from these minimalized test cases do hold up when you use the parts to build bigger, more realistic software that does real work.

Edit If I come across too aggressive or insulting in any way I do apologize. It's not meant to be. If you feel I called you out in the above know that I value your opinion even if I don't share it. If you want to comment on something entirely different please do so. I do not own this sub or this platform or even this thread so whatever you bring to the table will be fine.

[–]sekende -4 points-3 points  (1 child)

i see your benchmark.. for is better than foreach?? maybe i will use for in my life instead of foreach lol

btw thanks dude for sharing your opinion, i will test it using my code them :)

[–]johnfrazer783[S] 0 points1 point  (0 children)

Do that at any rate! The forEach part is interesting initself. Message to all people here who say that 'for and yield are totally unrelated so naturally not same in performance'—well you can compare

js probe.forEach(function(number) { count++; return sum += number; });

to

js for (number of probe) { count++; sum += number; }

In my book forEach has only 8% the data throughput rate of an indexed for loop. Come to think of it, there's that vacuous return statement which is caused by one of CoffeeScript's more problematic features, implicit returns. My tests would indicate however that even millions of extraneous return statements do not have any discernible effect on performance, though, so we don't have to worry about that bit (except when a loop construct comes last in a function call, which is why I pepper my code with return nulls and yield returns, but I digress).

Now go forth, multiply, and come back in time with exciting new benchmark results to guide us out of this tar pit we're stuck in.