66 comments

[ 2.7 ms ] story [ 134 ms ] thread
And here I was hoping that the title referred to something like "The most clever line of JavaScript is the line you don't write".
I 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”.
I think it was Bjarne Stroustrup who said something like - "don't be clever, be simple and obvious".

... still nice article... ;)

Was he trying to be funny after he did c++?!
It is possible to write simple and obvious code in C++. A lot of people just choose not to. ;-)
> 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".

>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.

> 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.

True, though languages should be designed so that it is difficult to write obscure, tricky or otherwise unreadable code.
For 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.

I 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.

Too bad he didn’t follow his own advise in design of C++ (at least the simple and obvious part)
(comment deleted)
The 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).

These 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]
For 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
Can 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.

I 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.

Yep, ["1", "2", "3"].map(x => parseInt(x)) works.
I 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.
There are generally two schools of thought: one is that the programmer should know better, memorize the documention and specs. The other one is that if it violates the principle of least astonishment it's just badly designed. Personally I believe that the truth is somewhere in the middle. It's good to know one's tools (including docs and specs) but I wouldn't call this kind of "clever" code maintainable.
You can work around this in lodash -- though of course it's not worth it:

  ["1", "2", "3"].map(_.bind(parseInt, null, _, 10));
  => [1, 2, 3]
I wrote one of these a few years back: 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)

Is it at least faster than the anonymous function?
That was my initial thought. Is there performance benefit in constructing a function call in this manner?
No, there is not - it is purely "language lawyer", it is actually slower in most cases - especially on newer versions of V8 that use TurboFan.
That might be clever, but aside from possible performance gains it's worse than the trivial alternative arr.map(it => it.trim()).
(comment deleted)
Take 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.
If 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.
Please 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.

Yup. 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.

Code should not be clever.

Good code is straight forward, carries no surprises and makes the whole task at hand look easy.

Clever is ok if it's still understandable or is needed to be clever to get the job done.
I would not go this far - writing code like this is fun. What you should never do is use code like this in anything important.
Important code has a way of starting out unimportant.
This 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.
I could _maybe_ see this code ending up in production code where performance optimization matters (if it actually fixes a performance issue). But if you did that you had better put a comment next to it clearly explaining why you had to do that for optimization purposes and how it works.
Agreed. Cool find, though.

Clever =/= robust or reliable, many times, and that's what they pay us the bucks for.

We 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-java...

> 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!

Yeah, I didn't understand this line, especially as at the very end, the author suggests just using the anonymous function anyways.
I would argue that it is not so clever.

1) It is less idiomatic

2) Does not provide a performance gain

Should be the top comment.

1) Accurate

2) Succinct

I kind of wish x.f(y) was the same as f(x, y) in javascript.
How would dispatching work exactly, e.g. how would you override valueOf or toString?
On 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.
That’s one of my favourite features of Nim.
I just benchmarked it: 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_AwjCCv...

If performance matters a manual for loop will be faster. `arr.map()` isn't all that fast (except in the recent Firefoxes).
I 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.

I added the floor loop to the code and made a console based webpage for it: 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.
Performing a Welch two-sample t-test in R, the null hypothesis that the two means are equal is rejected with p < .001
Is 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' ]
That 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' ]
>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.

Well, 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

I 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.

Debugging 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.
Or, more realistically: it will take twice the time. :-)
Seems pretty obvious for us who worked with JS before the arrow functions.
Is it actually any faster? Let's see...

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.)

Can confirm this. Clever version is actually about 10% faster with node v9.1
Actually, 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%

What's the point of writing a code like this? Giving maintainers a hard time? Showing off?
This 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.