
Blessed Are the JavaScript Developers - jwvcom
https://twitter.com/frontenddude/status/1300420130346864641
======
ImpressiveWebs
Just read the docs:

> If `compareFunction` is not supplied, all non-undefined array elements are
> sorted by converting them to strings and comparing strings in UTF-16 code
> units order. For example, "banana" comes before "cherry". In a numeric sort,
> 9 comes before 80, but because numbers are converted to strings, "80" comes
> before "9" in the Unicode order.

[https://developer.mozilla.org/en-
US/docs/Web/JavaScript/Refe...](https://developer.mozilla.org/en-
US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)

~~~
Gaelan
"it's documented" is no excuse for unexpected behavior.

(Relevant xckd: [https://xkcd.com/293/](https://xkcd.com/293/))

~~~
danShumway
I'm not sure it's unexpected.

Javascript has some weird behaviors, but in this case Arrays in Javascript can
contain multiple types. How would you sort an array of `['cat', 80.3, '80']`?
I think I would expect that to end up as `['80', 80.3, 'cat']`, not `[80.3,
'80', 'cat'].

Or to make things more complicated, `['cat', 80.3, { foo: 'bar' }, true]`. Do
we want to try and intuit whether that object gets converted to a string or a
value? Do we base that intuition on the prototype chain, or what?

I think it makes sense to cast every object/type in the array to a shared
format so everything can be compared based on the same criteria.

\---

Have simple rules that are universally applied:

\- All objects are converted to strings

\- Strings are sorted alphabetically

vs something weird like:

\- We try to intuit what type you want based on several common type
conversions and possibly the prototype

\- We have a set of built-in rules about which primitive types (number,
boolean, string, Symbol) comes before and after other primitive types.

~~~
naikrovek
> I'm not sure it's unexpected.

What do YOU expect when you ask someone to sort items that are _clearly_
numbers?

I don't know about you, but I do _not_ expect what actually happened.

~~~
danShumway
We don't know that the items are clearly numbers, arrays in Javascript are
untyped.

Your proposal is that Javascript's default sort algorithm should change
behaviors based on whether it can do an extra O(n) loop over the entire array
and check whether `typeof x === 'number'` evaluates to true for each index? I
don't think that's a good idea.

------
danShumway
I know this seems weird at first glance, but JS arrays are not typed. To all
the people saying this is obviously wrong, I'm curious what your intuitive
default strategy is to sort the following array:

    
    
      [
         7,
         '3',
    
         {},
         null,
         undefined,
         NaN,
         Infinity,
         new Date(),
         new Error(),
         Promise.resolve(),
         function () { return 'a'; },
    
         { toString: ()=>'a' },
         { toString: ()=>7 },
         { valueOf: ()=>'b' },
         { valueOf: ()=>9 },
         { __proto__: Number.prototype, 
           valueOf: ()=>'a', 
           toString: ()=>7 },
      ].sort()
    

The big thing everyone is always complaining about with Javascript is that it
tries to guess too much. Type conversions with equality operators, type
conversions with +/\- operators, lots of Javascript doing unexpected
conversions and trying to be helpful at the wrong times.

So sure, you _could_ have a set of random rules based on prototype chains to
figure out whether you were going to call `valueOf` or `toString` for any
given object, and you could come up with a bunch of rules for whether or not
primitive types like Symbols come before numbers.

Or, you could just say, "we're going to convert every value to its string
representation and sort alphabetically." At which point, as long as you
understand how string conversion works, it becomes very obvious and easy to
understand how the array above will get sorted.

The second option is easier to learn and easier to reason about. There's weird
stuff in Javascript, but if you're going to have a default (and there's good
reason to say that you shouldn't, and that `sort` without a parameter should
just throw an error), I think this is the _right_ choice for them to have made
about how the default should work.

Lots of intuitions about what the developer meant seem simple when you're only
looking at the simple case. But it is usually better to have one set of rules
that are easy to describe and that are universally applied. More of Javascript
should work this way, if we're going to have a default sorting algorithm, then
it's good that the default sorting algorithm mostly does the same stuff every
time no matter what you pass it.

This is not the kind of thing that makes me dislike Javascript, it's the other
stuff -- the inconsistencies and magic behaviors -- that give me more pause
about the language. But what Javascript is doing here is good.

~~~
brundolf
Python also has untyped arrays, and it:

1) Uses < for whatever types are present

2) Throws an error if two of those types are non-comparable

I think that's a much better approach than the implicit casting that happens,
frankly, all throughout JavaScript. I like JavaScript - more than Python,
overall - but this is one of those things where I don't think we need to be
defending the unfortunate decisions that were made in the past.

~~~
danShumway
> Throws an error if two of those types are non-comparable

The problem is that practically everything in Javascript is comparable. This
doesn't really solve the sorting conundrum, it just rephrases it. What's the
intuitive way to handle:

    
    
      5 < {}
      5 < '10'
      5 < 'hat'
      5 < function () {}
      5 < { toString: ()=>15 }
      5 < { toString: ()=>'15' }
      5 < { toString: ()=>15, valueOf: ()=>2 }
      ({}) < ({})
    
    

If your assertion is that all of those should just throw errors and we should
stop doing type inference entirely, then that's kind of reasonable, I guess,
but that's a much more drastic change to how Javascript works. If the
assertion is that thinking through Javascript operator conversions is easier
than thinking through string conversions, I just disagree with that.

Javascript's operator conversions are more complicated and have more weird
rules and edge cases. They are (somewhat justifiably) one of the biggest
things people complain about in the language. The last thing I want is to have
to explain to a new programmer why `5 < '10'`, and `5 > '3'`, but also `'5' >
'10'`. Given the way that Javascript is set up at its core, I think using
comparison operators for a default sort method would be a disaster.

Using `<` as it's implemented today would mean that even a simple array like
`[5, '10', '3']` would sort non-deterministically depending on what the
sorting algorithm was and what order the indices were compared with each
other. It's unusable unless you're going to change how Javascript operators
work.

~~~
mcphage
> What's the intuitive way to handle:

Most of the examples you provide are meaningless. What is the intuitive way of
comparing a number and a function? There isn't one, the question itself is
meaningless. Saying "hey look we found a way to come up with an answer by
turning everything into a string" isn't right either, since it means you come
up with the _wrong_ answer in plenty of situations—like in the original tweet.

I'd rather have a library that gives you the correct answer in important
common cases, at the expense of not being able to answer questions that don't
have an answer. The solution JS went with was to get the important cases
wrong, so that it could handle cases that don't have a meaning or use.

~~~
danShumway
> I'd rather have a library that gives you the correct answer in important
> common cases, at the expense of not being able to answer questions that
> don't have an answer.

The problem is that optimizing for the common cases and ignoring the difficult
or meaningless cases is exactly how you end up with 20 different rules for how
something should work that all handle the situation kind of well, but interact
with each other to create horrible, crazy bugs whenever anything unexpected
happens.

Look, Javascript type conversions are a disaster, I'm not going to pretend
otherwise. But the reason they are a disaster is specifically because the
language was so worried about making everything intuitive that it didn't stop
to think about what would happen when the half-dozen 'helpful' type
conversions interacted with each other in novel situations.

It's better to have one or two rules that work everywhere, even if they
occasionally surprise a new programmer. Beginners will only be beginners for a
small, finite amount of time, but they will need to deal with your language
quirks and emergent weirdness for the rest of their lives.

If more of the core Javascript language embraced that philosophy, it would be
a better language to program with.

It's the "don't worry about the edge cases, just make the obvious case right"
thinking that got us class syntax that doesn't actually build classes, and
strings parsed into numbers for `>` but not `+`, and basically the entirety of
`==`. Because all of those situations had slightly different answers about
what was the "intuitive" thing to do, and nobody thought any farther than the
isolated, immediately obvious use-cases for those features.

------
Minor49er
This tweet shows the following evaluation in JavaScript:

[6, -2, 2, -7].sort();

The result comes back with this unexpected set:

[-2, -7, 2, 6]

------
nlawalker
This should be the canonical example given in discussions about the “principle
of least surprise.”

~~~
sukilot
JavaScript cannot have "the" canonical example of that:

[https://www.destroyallsoftware.com/talks/wat](https://www.destroyallsoftware.com/talks/wat)

------
asdf-asdf-asdf
nobody thinks that this behavior is good. the problem is, this is how it was
at the beginning, and javascript will not break backwards compatibility. so we
have to live with it (or use a third-party sort-function, like
"lodash.sortBy").

there are other similar problems in javascript too:

\- "typeof null === 'object'". this is very wrong, but again,this is how it
was implemented at the beginning... you know the rest (
[https://developer.mozilla.org/en-
US/docs/Web/JavaScript/Refe...](https://developer.mozilla.org/en-
US/docs/Web/JavaScript/Reference/Operators/typeof#typeof_null) )

\- another problem with ".sort()" is that it both mutates the input-array and
returns the mutated input-array. so you write code like "const arr2 =
arr1.sort()", and the code works, but if you don't know the spec, you will not
know that "arr1" was modified and basically "arr1 === arr2".

~~~
AtlasBarfed
[https://www.netfunny.com/rhf/jokes/98/Mar/cobol.html](https://www.netfunny.com/rhf/jokes/98/Mar/cobol.html)

------
RcouF1uZ4gsC
This is one of the reasons I am so excited about WASM. I am playing around
with the Rust framework yew
([https://yew.rs/docs/en/intro/](https://yew.rs/docs/en/intro/)) to replace a
lot of my front-end Javascript programming, and it is a joy to use.

If you love Javascript that is wonderful, but I am happy to not be forced to
use it, when I would prefer other languages.

------
cj
V8 unit test for .sort() explains the behavior as...

> Default sort converts each element to string and orders lexicographically.

[https://chromium.googlesource.com/v8/v8/+/4.3.18/test/mjsuni...](https://chromium.googlesource.com/v8/v8/+/4.3.18/test/mjsunit/array-
sort.js?autodive=0%2F%2F%2F%2F#36)

~~~
mirkules
“You’re using it wrong” is a poor argument for undesired/unexpected behavior
(aka, a bug).

~~~
cj
I actually stumbled across the V8 link while looking to see if there were a
bug logged for this behavior somewhere. I agree it seems like a bug.

I'm definitely not saying "You're using it wrong".

~~~
mirkules
Oh, I was not insinuating you were. I was directing my comment into the V8
developers’ direction

------
rektide
Jokes on you we inheriting this planet.

Don't sweat the small stuff.

------
soal
This joke was already old fifteen years ago.

~~~
sukilot
There are JS devs who weren't alive 15 years ago.

------
syspec
> [-2, 1,5,-7].sort((a,b)=> a-b) works fine

~~~
manquer
It is not obvious that you need to include comparative function for a
straightforward numerical sort ? How many developers are going to miss that ?

~~~
Supermancho
> It is not obvious that you need to include comparative function for a
> straightforward numerical sort ?

No. It's not obvious.

Python -

    
    
        a = [6, -2, 2, -7]
    
        a.sort()
    
        print(a)
    

PHP -

    
    
        $arr = [6, -2, 2, -7];
    
        sort($arr);
    
        print_r($arr);
    

Rust -

    
    
        let mut vec = vec![6, -2, 2, -7];
    
        vec.sort();
    
        assert_eq!(vec, vec![-7, -2, 2, 6]);
    

The expected behavior is a default natural number sort.

What other default was chosen? String comparison for some value of Strings,
coerced from the integers in some way.

If you have to research a bunch of implicit behavior to understand how sort
works for numbers. I believe that's unintuitive by definition (simplest case
is more complex than necessary). Fixable with a required flag or specialized
functions (eg PHP).

~~~
SamReidHughes
It was more intuitive back in the day when everybody knew Perl.

