6 ms·
A Clever Line of JavaScript
- JoshTriplett 9y agoAnd here I was hoping that the title referred to something like "The most clever line of JavaScript is the line you don't write".
- tomalpha 9y agoI realise you might be being lighthearted, but just to be boring I’ll thrown in “The fastest and most bug free line of code in any language is the line you don’t write”.
- cominous 9y agoI think it was Bjarne Stroustrup who said something like - "don't be clever, be simple and obvious". ... still nice article... ;)
- k__ 9y agoWas he trying to be funny after he did c++?!
- krylon 9y agoIt is possible to write simple and obvious code in C++. A lot of people just choose not to. ;-)
- camus2 9y ago> It is possible to write simple and obvious code in C++. A lot of people just choose not to. ;-) You still need to be C++ fluent enough to be able to read other people's code. And it's not like everybody has the same definition of "simple and obvious".
- coldtea 9y ago>And it's not like everybody has the same definition of "simple and obvious". No, but it's like we can expect of people to more or less agree on a definition of "simple and obvious" or else be outliers.
- krylon 9y ago> And it's not like everybody has the same definition of "simple and obvious". That is a very good point. I had an interesting discussion with a Perl programmer once regarding the use of Perl's default variable $_ - to him, code that relied heavily on it was straightforward and readable. But to a newcomer, it can be very tricky to figure out what is going on. And even to an experienced Perl programmer, it can increase the mental overhead of parsing code significantly if s/he is not used to it.
- dvddgld 9y agoTrue, though languages should be designed so that it is difficult to write obscure, tricky or otherwise unreadable code.
- krylon 9y agoFor better or worse, languages that put am emphasis on readable code tend to remain not-so-popular. Python is an example, but Ada or Pascal are relatively unpopular these days. But that is one thing I like about Go, because "idiomatic" Go code tends to be very straightforward and readable.
- dvddgld 9y agoI mean C++ isn't going to disappear anytime too soon, but I like where we're headed with language design. Python isn't just popular, it's monumentally popular, and I think that's a great sign. In my eyes, Ada and Pascal aren't missed, precisely because there are better choices for new projects. I love what they've done with Go from my experience with it so far, they hit the target on a number of design fronts.
- darthlord382 9y agoToo bad he didn’t follow his own advise in design of C++ (at least the simple and obvious part)
- deleted 9y ago[deleted]
- edejong 9y agoThe competent programmer is fully aware of the strictly limited size of his own skull; therefore he approaches the programming task in full humility, and among other things he avoids clever tricks like the plague. Dijkstra (1972) The Humble Programmer (EWD340).
- hdhzy 9y agoThese kind of hidden extensibility in built-in functions make even seemingly simple code not work as expected: ["1", "2", "3"].map(parseInt) > [1, NaN, NaN]
- andyfleming 9y agoFor anyone wondering why this is, it's because parseInt takes two arguments "string" and "radix". Map calls a function with "currentValue", "index", and "array". So the calls essentially look like this: parseInt("1", 0, arrayReference) // => 1 parseInt("2", 1, arrayReference) // => NaN parseInt("3", 2, arrayReference) // => NaN
- czechdeveloper 9y agoCan you please explain this? It makes no sense to me (as c# developer) that `["1", "2", "3"].map(parseInt)` whould get different result from `["1", "2", "3"].map(a => parseInt(a))`. Why are they not equivalent? EDIT: Never mind, another comment explained this.
- prolurker 9y agoI would argue this particular example has more to do with the radix parameter of parseInt being optional and having a complex behavior. More generally, javascript functions accepting any number of parameters, regardless of those specified in the function declaration, is quite error prone when passing functions around. I always use anonymous functions or 'bind' to explicitly match the parameters unless all functions involved are curried. The other reason to avoid passing 'naked' functions around too happily is the behavior of this. I also find most optimizations to focus on simple, explicit code. Nothing like using the less common, more dynamic features of the language to hit deoptimizations.
- ece 9y agoYep, ["1", "2", "3"].map(x => parseInt(x)) works.
- jasonkostempski 9y agoI wouldn't call it "hidden". If someone expected that to work, or couldn't figure out what the article example was doing, they just haven't read the documentation for 2 common functions. In fact, last I checked, always using the second parameter of parseInt was considered good practice.
- inglor 9y agoI wrote one of these a few years back: https://stackoverflow.com/questions/18947892/creating-range-in-javascript-strange-syntax https://stackoverflow.com/questions/18947892/creating-range-... - honestly I hate this sort of cleverness in real code but it makes for interesting puzzles. (As long as no one actually asks these questions in interviews)
- bonzini 9y agoIs it at least faster than the anonymous function?
- CapacitorSet 9y agoThat might be clever, but aside from possible performance gains it's worse than the trivial alternative arr.map(it => it.trim()).
- deleted 9y ago[deleted]
- amptorn 9y agoTake care with this technique. We know that `Array.prototype.map` passes not one but three parameters to the callback function: the array element, the array index and the array itself. As a result of this, addressParts.map(Function.prototype.call, String.prototype.trim) is roughly equivalent to addressParts.map((str, i, arr) => str.trim(i, arr)) Fortunately for us, `String.prototype.trim` ignores those additional parameters and only uses `this`. But if it didn't, you could end up with some head-scratching behaviour.
- partycoder 9y agoIf you wish you verify why is this, consider the following. Forget about String.prototype.trim at the moment. Consider this function: const f = function() { console.log('receiver is', this, '. arguments are', arguments) } This function prints the receiver (aka "this"), and arguments. If you use it in the following way: ['a', 'b', 'c'].map(Function.prototype.call, f) The output is receiver is [String: 'a'] . arguments are { '0': 0, '1': [ 'a', 'b', 'c' ] } receiver is [String: 'b'] . arguments are { '0': 1, '1': [ 'a', 'b', 'c' ] } receiver is [String: 'c'] . arguments are { '0': 2, '1': [ 'a', 'b', 'c' ] } Meaning, that the receiver (aka "this") of the function will be the array element, and the arguments will be the array index and the array itself.
- peterkelly 9y agoPlease don't do this. It might be clever, and it's the sort of thing that would be pretty cool to include in a CS lecture about higher-order functions, but it won't earn you the gratitude of fellow team members who have to maintain your code after you leave.
- MichaelApproved 9y agoYup. If someone has to spend 30 minutes trying to understand one line of code, you're doing it wrong. I'll take 10 lines of code that clearly self documents with elaborately named variables over this 1 line of magic.
- konschubert 9y agoCode should not be clever. Good code is straight forward, carries no surprises and makes the whole task at hand look easy.
- jonny_eh 9y agoClever is ok if it's still understandable or is needed to be clever to get the job done.
- danieltillett 9y agoI would not go this far - writing code like this is fun. What you should never do is use code like this in anything important.
- ocharles 9y agoImportant code has a way of starting out unimportant.
- danieltillett 9y agoThis is true, but including code like this should make it easier to know you should rewrite if you are going to use it in something important. I have been bitten more often by quick hack code that looks OK on the surface than code that is full of clever code like this.
- amptorn 9y agoWe can go one cleverer[1]: functions.forEach(Function.prototype.call, Function.prototype.call) this is approximately equivalent to functions.forEach(f => f()) although in practice it's actually more like functions.forEach((f, i, arr) => f.bind(i)(arr)) so let's hope `f` isn't too sensitive to the value of `this` or that `arr` parameter... [1] https://zpao.com/posts/calling-an-array-of-functions-in-javascript/ https://zpao.com/posts/calling-an-array-of-functions-in-java...
- z3t4 9y ago> we strive here to avoid creating new functions Avoiding creating new functions or objects is a good optimization strategy, but there's most likely very little gain as the optimizer will most likely inline the functions. Always do the super naive, easier to understand version first, and only optimize after you have identified the bottleneck and measured the performance!
- throw_away 9y agoYeah, I didn't understand this line, especially as at the very end, the author suggests just using the anonymous function anyways.
- partycoder 9y agoI would argue that it is not so clever. 1) It is less idiomatic 2) Does not provide a performance gain
- emacsgifs 9y agoShould be the top comment. 1) Accurate 2) Succinct
- Tarean 9y agoI kind of wish x.f(y) was the same as f(x, y) in javascript.
- masklinn 9y agoHow would dispatching work exactly, e.g. how would you override valueOf or toString?
- Tarean 9y agoOn retrospect I probably want it to be more like lua? f(this, other) -> f.call(this, other) obj.f(other) -> obj.f.call(obj, other) and then replace implicit this with the first argument? It is a tiny but more verbose but gets rid of all the confusion around implicit this rules so it seems like a win.
- girvo 9y agoThat’s one of my favourite features of Nim.
- scarlac 9y agoI just benchmarked it: https://gist.github.com/scarlac/87dc480c9f5893060a17a4c6f61cbc85 https://gist.github.com/scarlac/87dc480c9f5893060a17a4c6f61c... Personally I don't find the results convincing. With 20k iterations and 18 trials I have to squint to see a practical difference. But please, judge for yourselves. Numbers: https://docs.google.com/spreadsheets/d/1mZx7ENLTeCG0G_AwjCCv71Bc2MYICfvGoyDvHAeOOYY/edit#gid=0 https://docs.google.com/spreadsheets/d/1mZx7ENLTeCG0G_AwjCCv...
- susi22 9y agoIf performance matters a manual for loop will be faster. `arr.map()` isn't all that fast (except in the recent Firefoxes).
- masklinn 9y agoI get similar performances for the "naive" map: let trimmed = strings.map(str => str.trim()); and a c-style for let trimmed = []; for(var i=0; i<strings.length; ++i) { trimmed.push(strings[i].trim()); } in both Chrome (62) and Firefox (58). The c-style for is very lightly faster in FF (not Chrome) but it's 767 ops/s to 725 ops/s so not exactly brain-breaking (chrome is at 454 and 452). Safari (11) is the only one where map is a fair bit slower than c-style (420 to 377). In all cases, both the clever map and for..of are slower than the naive map, though the magnitude varies Chrome yields very little difference but Firefox and Safari dislike for..of and like "clever map" even less.
- jostylr 9y agoI added the floor loop to the code and made a console based webpage for it: http://jostylr.com/cleverdumb.html http://jostylr.com/cleverdumb.html Kind of hackish, but you can open it up in a browser and look at the console for the run times. Basically, the clever code is generally worse, and the arrow versus for loop in Firefox and Safari are similar. But in Chrome, the for loop is quicker; it was far more noticeable in node than in chrome. One quirk of chrome is that the first time the for loop is run, it seems to take longer than subsequent runs and I am not sure why that is. I tried moving around initialization stuff and it did not seem to make any differences. Times from node: Prototype: 105.467ms Arrow function: 106.674ms For: 98.820ms Prototype: 105.743ms Arrow function: 106.192ms For: 82.733ms Prototype: 105.512ms Arrow function: 106.517ms For: 82.625ms Basically, code in the comfortable style. I used to love for loops, but have now switched over to map and forEach when it makes sense to do so.
- arh68 9y agoIs this really better than the regex way? > '\t1 ;2;3;4;5;6;7;8;9\r\n;10\n'.split(';').map(Function.prototype.call, String.prototype.trim) [ '1', '2', '3', '4', '5', '6', '7', '8', '9', '10' ] > '\t1 ;2;3;4;5;6;7;8;9\r\n;10\n'.match(/[^;\s]+/g) [ '1', '2', '3', '4', '5', '6', '7', '8', '9', '10' ] > '\t1 ;2;3;4;5;6;7;8;9\r\n;10\n'.match(/\d+/g) // this ones' a bit different [ '1', '2', '3', '4', '5', '6', '7', '8', '9', '10' ] EDIT: or even (I'm still thinking here) > '\t1 ;2;3;4;5;6;7;8;9\r\n;10\n'.trim().split(/[;\s]+/) [ '1', '2', '3', '4', '5', '6', '7', '8', '9', '10' ]
- pygy_ 9y agoThat supposes that your values either don't have embedded white spaces or are completely numeric... So it's not really equivalent. > '\t1 ;2 a;3;4;5;6;7;8;9\r\n;10\n'.split(';').map(Function.prototype.call, String.prototype.trim) [ '1', '2 a', '3', '4', '5', '6', '7', '8', '9', '10' ] > '\t1 ;2 a;3;4;5;6;7;8;9\r\n;10\n'.match(/[^;\s]+/g) [ '1', '2', 'a', '3', '4', '5', '6', '7', '8', '9', '10' ] > '\t1 ;2 a;3;4;5;6;7;8;9\r\n;10\n'.match(/\d+/g) [ '1', '2', '3', '4', '5', '6', '7', '8', '9', '10' ] Edit (re. your edit): > '\t1 ;2 a;3;4;5;6;7;8;9\r\n;10\n'.trim().split(/[;\s]+/) [ '1', '2', 'a', '3', '4', '5', '6', '7', '8', '9', '10' ]
- coldtea 9y ago>Is this really better than the regex way? Considering that it's built in, probably optimized C++, and does just what it says on the tin, then yes.
- arh68 9y agoWell, you'd be wrong by a mile! On my machine, the .map(..) way is almost twice as slow. It ain't even close. But you're right, I failed to consider that it's "probably optimized". And "C++". /s
- coldtea 9y agoI don't see how the "sarcasm" (I use the term loosely) is justified. You indeed failed to consider all those things, and all those things are things one should have in mind, whether a particular built-in function is faster than a custom implementation or not. Not to mention that I said "probably optimized C++" -- I didn't claim straight out that this is the case. The other two claims still hold: it's built in, so you don't need to roll your own regex based one (complete with bugs the first time for most people), and it conveys the intention to trim perfectly. That's even better than being faster, unless there's a real need to have that particular operation be fast in your code. Heck, the fact that you got them wrong in your initial regexes (not capturing what trim does in general) shouldn't give you enough pause? If you have gone with those BS regexes instead of trim you would have introduced a bug in the codebase. If one wants to optimize some such method, running on a tight loop, and on a verified hot spot of their code, they would of course need to measure to see which method is faster, not just rely on someone's suggestions. That's computer science 101. That it doesn't work "on your machine" (and on whatever browser you've checked) doesn't mean anything. One would need to check on various browsers, teat what browsers they want to target (e.g. perhaps their app is Electron and only needs to work there), and which method is faster there.
- nikanj 9y agoDebugging code is twice as hard as writing it. If you write code as cleverly as you can, you'll be too stupid by half to debug it.
- reitanqild 9y agoOr, more realistically: it will take twice the time. :-)
- dsego 9y agoSeems pretty obvious for us who worked with JS before the arrow functions.
- tomatsu 9y agoIs it actually any faster? Let's see... https://jsperf.com/trimcall/1 https://jsperf.com/trimcall/1 For me, the "clever" one is slightly faster in Chrome while the supposedly naive one is significantly faster in Firefox. Looks like the naive approach not only wins in terms of readability, but performance, too. (Unless I seriously screwed this up which is always a possibility with micro benchmarks.)
- cominous 9y agoCan confirm this. Clever version is actually about 10% faster with node v9.1
- cominous 9y agoActually, the for-based loop is another 10% faster - so if performance is relevant, just use the for-loop! Node v9.1 + benchmark.js clever-solution x 249,543 ops/sec ±0.36% standard-arrow-function x 213,926 ops/sec ±0.35% old-for-loop x 261,449 ops/sec ±0.54%
- sAbakumoff 9y agoWhat's the point of writing a code like this? Giving maintainers a hard time? Showing off?
- jwdunne 9y agoThis is a great example of why you should avoid writing clever code. A clever one-line piece of code requires a blog post to make sense of it. Not slating the article. I thought it interesting and the author does mention this caveat, making a point of saying the typical way is honestly more readable. As for the project they found this in, I hope this stuff isn't thread through it, for the maintainer's sake and sanity.