> is not realising it uses string comparison by default
Hell, really?? That's gross.
As for the parent's comment about a dev "not realising array.sort() was unstable", the dev should know his tools. I can't see anyway why a stable sort is better, I've never sorted on x then sorted on x, you just sort on x & y.
> It’s the curse of the optional arguments. Same thing with parsing numbers where the second argument magically specifies the base.
Stupid defaults is not "the curse of optional arguments", it's the curse of stupid defaults.
Most languages have defaults for these two operations yet have proper defaults rather than stupid ones. Python's list.sort() and int() certainly do, so do Java's Collections.sort() and Integer.parseInt().
Uniquely awful defaults is a historical (and defining) feature of javascript, not of having default values, or optional arguments.
The horror that arises in js is due to a combination of unfortunate things like function arity, and coercion. The one I was thinking of is the famous
[1,2,10].map(parseInt)
It’s several things that on their own aren’t mad which taken together produces a crazy result. 10 as the default for an optional second argument of parseInt is fine. But map should only use a single argument closure by default. So map(parseInt) must be equivalent to map(x -> parseInt(x)).
It’s not the stability that bothers me but the fact that the comparison function is implicit and basically is
(a, b) -> a.toString().compareTo(b.toString())
Unlike say int.parse() where 10 is a reasonable default for radix, doing string comparison as default is almost never what the caller wants, so is a bad default. Functions where there is no obvious (99% of the time the desired value) should not have a default value when the argument is omitted. I’d much rather have things.sort() just fail and force me to specify the comparison, than to see it sort alphabetically.
As a side note: a second optional argument specifying stability true/false would be reasonable. True could be the default too, since unstable sorting is effectively an optimization with a tradeoff.
When neither the function nor the parameters have hard types, you have to create heuristics. There could be a test for numbers there, but it would also be surprising because at the older days people expected "10" and 10 to behave the same.
Most would expect 10 to parse as an integer. To specify a string, most would be happy with putting quotes around it. So I don't think dynamic types fully explains the bizarre behaviour.
It's not because of "weak types" since the types inside the array don't get their type information erased. It's because Array.sort takes a comparison function but the default function instead of being something like
[1, 10, 2].sort((a,b) => a > b ? 1 : -1)
// -> [1, 2, 10]
Both of your examples have invalid sort comparator functions.
A JavaScript sort comparator is required to not only return a positive or negative value, it must handle all three cases: greater than, less than, or equal. When the arguments compare equal, the comparator must return 0.
Returning only a positive or negative value is a very common error. (I don't mean to pick on you for this! I have seen the error so often that I hope this may be helpful for anyone writing a sort comparator.)
The invalid comparator will force the normally stable sort to be unstable, because sort() now has no way to know that two items should be considered equal and their existing order preserved.
Let's demonstrate with the "doggos" example that the article links to:
(Note that this implements a reverse sort as can be seen in the output on the test page.)
Subtraction provides the proper return values when two numbers are compared: any negative number, any positive number, or zero when the ratings are equal.
Another valid comparator (again using a reverse sort) would be:
doggos.sort( ( a, b ) =>
a.rating < b.rating ? 1 :
a.rating > b.rating ? -1 :
0
);
Either way you get the expected output in the current version of Chrome, which has a stable sort. Each group of doggos with the same rating appear in their original order:
That, incidentally, is why key functions (aka native support for decorate-sort-undecorate) is so convenient: rather than a fiddly and error prone comparator, the key function just returns a comparable (a value or composition of values).
A good alternative for statically typed languages is to have the comparison result be a proper sum type, and provide ways to generate and compose comparisons e.g. Haskell or Rust's Ordering (and Ord trait / typeclass).
> developer didn’t realize Array.sort() was unstable
You seem to think stable sort is much more important than most of the rest of the field.
By default, C sort isn't stable, C++ sort isn't stable, Ruby sort isn't stable, C# sort isn't stable, Perl sort isn't stable. Hell, not even GNU's sort utility is stable.
JavaScript has some unexpected idiosyncracies, but sort() stability isn't one of them.
First thing I thought about was runtime monkeypatching - while this could be done via simple string replacement during build time (via webpack/rollup etc plugins) now you will be able to do it runtime as well.
Angular would see that you are looking for a parameter named myCoolService, see if it already has it, and then inject it when calling your controller.
This of course broke in various ways with minification when the parameter names were mangled. So one had the opportunity to use "array syntax", requesting a dependency by a string. Or use a preprocessor in the build script adding the array syntax before minifying.
Did it by any chance pull parameter names out by using a magic-number index into the function text? If so, that'd be broken by the 'function /* a comment */ foo () {}' change in the article (and might be what I remember having seen a long time ago). Glad I don't have to deal with that sort of thing. Does Angular 2 do it the same way?
Lots. I can't remember exactly what I've seen, but believe I have seen it used in interesting ways. Including (drum roll...) .toString().substring(some_magic_number, ...) to do something or other (wish I remembered what). I'll let you think about whether that's wise, but note that indexing into 'function /* a comment */ foo () {}' (the example from the article) will give different results from indexing into 'function foo () {}'.
Really liking these new developments! JavaScript is not the horrible language it used to be anymore (in my very subjective opinion). When I started writing JavaScript in 2016 after a Python background I was frustrated every day... Then after some time and learning about which dark corners to avoid, which tools to use, etc. it became quite an enjoyable experience. Nowadays I mostly use a mix of JavaScript and TypeScript depending on the projects and it's been great.
One thing that I am a bit afraid about though is that the language might become more complex and complicated over time because of backward compatibility (C++-like?). I saw that they will be introducing a new function "replaceAll(...)" to avoid the weird semantics of "replace(...)" (stopping after first occurrence unless a 'global' RegExp is passed as argument). And that's great, but that's a new function, and maybe 'replace(...)' should have had this behavior from the start. I hope to be wrong on this one!
> One thing that I am a bit afraid about though is that the language might become more complex and complicated over time because of backward compatibility (C++-like?).
New methods with simple behaviour don't really make the language more complex through.
> And that's great, but that's a new function, and maybe 'replace(...)' should have had this behavior from the start.
As you note it kinda does, just in a weird roundabout manner.
One issue with javascript is also that it makes extending existing functions difficult, because extra arguments are just ignored by default, so you can't just add a `count=1` parameter to String.replace to override the regex flag as there could be code out there which already passes a third (currently ignored) parameter and would break.
> New methods with simple behaviour don't really make the language more complex through.
I think they do, in some ways. If you have multiple methods or functions which seem to do similar things, as a new-comer it can be incredibly confusing. I remember when I started I never knew what the "best way" to iterate on things was... for loops? for in? for of? foreach? And it was not obvious which one to use or what were the differences. I agree that it might be less ambiguous between "replace" and "replaceAll", but I think over time these things matter.
> I remember when I started I never knew what the "best way" to iterate on things was... for loops? for in? for of? foreach? And it was not obvious which one to use or what were the differences.
Possibly aside from the latter (assuming you mean Array#forEach), these are not different "methods with simple behaviour", they're different language constructs with largely overlapping behaviour & use cases.
Sorry if I expressed myself poorly but that is exactly the point I wanted to make. To maintain backward compatibility, new constructs with partial overlap with existing ones are introduced; and this overall increases the complexity of the language. Whereas breaking backward compatibility would allow to re-use existing constructs and change their semantics (I am not saying this would be a better path though).
The difference between JS and C++ is that C++ is addressing a complex problem domain whereas JS is addressing a simple one. So JS can put its complexity budget towards features that improve productivity whereas such features in C++ require huge amounts of complexity. Take for instance anonymous functions in JS compared with lambdas since C++11, the C++ version needs a complicated capture syntax, capability for capture by value and reference, type inference, templating, and more. All that complexity is useful and needs to be there (I have used most of it in my own code) - in JS anonymous functions are immensely simpler. That's why JS will never become even 1/2 as complex as C++.
> C++ doesn't actually need it. Rust proved that capture everything by reference or capture everything by move is enough for all practical use cases.
That's kinda true but kinda not: precise "capture clauses" is a common design pattern in rust[0] showing that the flexibility is extremely useful, and I think I've seen rumblings about adding them to the language.
I mean technically you only ever need `move` closure, "ref" closures are already a convenience.
Honestly if I hadnt gotten into Python and learned some amazing ways to write code I would of never have been as productive with JavaScript as I have been all these years. It was painful at first but over time I have learned the quirks.
Not ES directly, but you know what I need on an almost daily basis? A JSON date type. It’s obnoxious to have to pass a string back and forth and parse it on either end between server and browser.
There's plenty of good reasons one doesn't exist. JSON is not JS-specific. It is a standard interchange format used by thousands of languages, many of which have very different date implementations.
If you did have a JSON date, how would you decide what it was? Would it be a timestamp, or a civil date-time? Would it have timezones? Offsets? Locations? Would there be a database along with it required to understand it correctly?
A 'date' type could be as simple as syntax indicating that a particular ISO8601 string IS a date. Right now, there's no reasonable way to infer that unless you're already aware of the schema of the JSON object you're receiving.
You could make the same argument with numbers - there's no reason you couldn't just pass strings containing the number - but there's advantages to being able to distinguish between a string and a number when the schema is unknown.
That is not a good reason though. The spec could just decide on the wire format like with strings and numbers, and clients would have to translate into the date type of the language or platform.
The reason JSON doesn't have dates is simply because JavaScript doesn't have date literals. You can write new Date(...) in JavaScript, but allowing that in JSON would have opened a whole can of worms.
JSON numbers are....I don't know what they even are. JavaScript -- the originator of JSON -- can't support 64-bit integers in JSON. Nor can it support decimal fractions in JSON, despite JSON using decimals. And yet JSON numbers also aren'ty IEEE-754 floating point numbers, as they lack +/-Infinity and NaN.
JSON numbers are ill-defined and unique from every other "number" concept in any language or format.
I was going to ask a variant of that question: what is the status of BigInt and when might it clear TC39 approval? In most languages a Date object is just a BigInt with Unix time() making this somewhat obvious. BigInt will allow not only proper Date implementation but Currency and Real types, and many more things.
If by BigInt you mean an arbitrary-precision integer type, then AFAIK no major language uses it to represent dates.
If you mean a 64-bit integer type (which, to be fair, is 11 bits larger than what JavaScript supports), then that's still not enough. As long as you want nanosecond-level granularity, and range beyond the current decade for your timestamps, you'll need more than 8 bytes to represent them in code.
I would argue dates with nanosecond granularity are of limited usefulness. In almost all use cases millisecond granularity is sufficient for dates, if you need nanosecond granularity chances are you also want a monotonic clock instead of real time that's subject to daylight savings, NTP correcting the clock etc.
I think it's mostly a usability tradeoff. If you can use the same data type for calendar appointments and high-precision performance logging, then you save your users the necessity of choosing between different types. Bonus points for being able to get away with a single nanosecond-based Duration type that has straightforward arithmetic with nanosecond-based timestamps.
And if you're deep enough to worry about the cost of those extra bits, you probably want to have your own date type anyway, tuned for your exact problem.
BigInt is in Stage 4 (https://github.com/tc39/proposal-bigint) which means that more than 2 implementations are shipping it unflagged. This is the last stage of any proposal in the TC39 process, which means it is de facto part of the standard.
You can use an unix epoch (seconds or milliseconds from 1/1/1970) to make parsing very fast. This is naturally supported by Javascripts new Date() too.
its ok for instants but not dates: receiving side may truncate the instant to a date in a different timezone env than the sender (easily done and will NOT be UTC by default in many envs) -and then you may have an off-by-one on the day.
That's not the point, the point is an instant is not a date. And common ways of getting the date from an instant are likely to give you the date of the instant interpreted in your current timezone for your computing env (which is often what is wanted such as when displaying for a user), so when you extract the date portion, it is not the intended one. So it's not that there's an easy right way of doing it, of course there is - but there are also easy wrong ways which also seem obvious.
Also, forget about the receiving side, you're also assuming that the sending side meant the date for the instant as at offset 0 instead of in their own timezone (which is probably how the instant is being displayed for them). From experience, that's not a safe assumption - witness any instant meant to mean "today's date here and now" derived from a now() call alone or truncated to have 0 time parts (in current time zone!) - I see that attempted a lot. Way too error prone especially when you don't control both sides.
Yes it is. An instant expressed as a Unix epoch is a date in UTC. The sender converts any non-UTC date to UTC and then to unix time. The receiver parses unix time to a UTC date, and then converts to any timezone necessary. What's the problem?
If you're using code that doesn't know how to handle UTC or converts it automatically to some server/env timezone, or doesn't check the timezone of the dates then that's a problem with that code, and has nothing to do with JSON or unix time.
There was no point other than incorrectly parsing a unix timestamp to something other than UTC. If you need to pass the time zone then pass it separately or use an ISO format instead. None of this is that complicated.
The point is that a date is not the same thing as an instant. A unix timestamp captures an instant, but that may not be suitable or desirable to a specific use case e.g. instants are usually broken when it comes to scheduling future events as the mapping might change inbetween the recording and occurrence, and you want this change to be reflected on your execution.
I believe his point was that an instant in time (be it seconds since epoch or represented in some other way) is not the same as the representation of a calendar day - string-formatted standards let you represent concepts other than an instant.
The confusion might be coming from JavaScript incorrectly calling its instant representation Date, when it is actually quite a bit more precise than that.
Its also worth noting that you can't pick a time like 12:00 UTC and have it retain the calendar day in all time zones, because there are a few stray islands which are UTC+14 rather than UTC-10.
It does not do it automatically. Even in your example, you call `Response.json()` to do the parsing. If you change that to `Response.text()` it will be text instead. If you don't include the `.json()` call, nothing will be parsed from the body.
I was going to argue with you, that there are plenty of useful types. Temporal types are just one of them and requiring a dedicated syntax might be overkill. But now I thought more about it and honestly temporals are the most frequently used data type for interaction between systems outside of basic ones. So actually it would make a lot of sense. It could be just ISO-8601 literal. Something like `{"date": 2019-12-15, "timestamp": 2019-12-15T23:49:55.123456, "time": 19:57, "timestampwithtz": 2019-12-15T23:49:55.123456-06:00}`. It starts like a number but follows a dash or twocolon which indicates that it's not a number, but a temporal value. Should not be a problem to parse.
[This comment is wrong; see masklinn’s response for my misunderstanding.]
> Now the function would rather insert an escape character before the character code so that the result is still readable and valid UTF-8/UTF-16 code:
> JSON.stringify('\uD83D');
> // '"\\ud83d"'
I’m inclined to consider this a misfeature, unbreaking something that I’m glad was broken and should have remained broken.
Unpaired surrogates are (to simplify terminology a little) basically invalid Unicode. On the web platform, the only situation where unpaired surrogates should be encountered is as a transient state on user input, on platforms that send non-BMP characters through in two pieces (which is most browsers on Windows). Beyond that, nothing should ever deal in unpaired surrogates, because they will make things blow up and your life miserable.
Unpaired surrogates cannot be represented in UTF-8, or in well-formed UTF-16 (and that’s where JavaScript did the annoyingly bad thing that we’re paying for decades later, going with UTF-16 and not requiring well-formedness). Hence I’d quibble with the description of the result as “valid UTF-8/UTF-16 code”. (Sure, the actual JSON byte stream will be valid, but it contains an escaped string that is not valid.) U+FFFD REPLACEMENT CHARACTER was at least as valid, arguably more valid.
In the I-JSON restricted subset, which is what I’d say everything should actually limit itself to, such strings are disallowed and will cause parse failure: https://tools.ietf.org/html/rfc7493#section-2.1.
You're misreading the article section, although the article is also partially wrong: the old behaviour was to output the lone surrogate in the JSON stream[0] (the "�" here is confusing: it is a display artefact of the specific font, not U+FFFD), the new behaviour is to replace the unpaired surrogate by the literal 6-character long escaped representation of the unpaired surrogate.
That is, formerly U+D83D would pass through straight as U+D83D (or possibly be replaced by U+FFFD in some implementations), whereas it is now "encoded" as the sequence U+005C U+0075 U+0044 U+0038 U+0033 U+0044.
That is, the entire point of the "well-formed stringify" proposal[1] was to stop generating broken JSON:
> Rather than return unpaired surrogate code points as single UTF-16 code units, represent them with JSON escape sequences.
[0] it's possible that some implementations would replace the lone surrogate by U+FFFD but according to MDN:
> Before this change JSON.stringify would output lone surrogates if the input contained any lone surrogates; such strings could not be encoded in valid UTF-8 or UTF-16
this is also the behaviour I observe on an old safari, and the one which is quoted in the tc39 proposal[1]:
> JSON.stringify can return strings including code points that have no representation in UTF-8 (specifically, surrogate code points U+D800 through U+DFFF). And contrary to the description of JSON.stringify, such strings are not "in UTF-16" because "isolated UTF-16 code units in the range D800₁₆..DFFF₁₆ are ill-formed" per The Unicode Standard, Version 10.0.0, Section 3.4 at definition D91 and excluded from being "in UTF-16" per definition D89.
The article does contain an error here, for the code point on the page is actually U+FFFD, not U+D83D (which of course is unrepresentable there). I trusted that (being a little surprised that it’d do the replacement, which is more what I’d expect of Python, but accepting it all the same) and didn’t pull out an old browser to confirm what actually happens, which is, as you say, an unpaired surrogate code point.
The article should probably say '"\ud83d"' instead of '"�"' (even though the number of backslashes still then requires thought), because yielding U+FFFD there would be a completely valid (and preferable, in my opinion) solution.
I love when the reaction to being wrong is to explain how the thing is responsible for their error and so they waste a few sentences explaining how it could have avoided leading then astray.
> The article does contain an error here, for the code point on the page is actually U+FFFD, not U+D83D (which of course is unrepresentable there).
The MDN page also does that, that's a bit misleading but TFA commits way worse a sin: it actually states the unpaired codepoint is swapped for the replacement character:
> In earlier versions, these would be replaced with a special character:
which is just wrong.
> yielding U+FFFD there would be a completely valid (and preferable, in my opinion) solution.
I agree. The proposal does not explain whether U+FFFD was considered, I expect they picked the escaped version to limit or avoid data loss: if you get an U+FFFD you have no idea what it used to be.
fromEntries(), aside from the symmetry with entries() is a really neat feature.
For example `Object.fromEntries(new URLSearchParams(window.location.search))` will parse a query string into a nice JS object. This works because the iterator of the search params class returns tuples.
What is already available today is `new URLSearchParams(Object.entries(params))` for turning a "params" object into a query string.
Different browsers will update at different times (or not at all). There are websites like caniuse and mdn that will show you what browsers are currently supporting. If you are working on an intranet site, you might know that everyone will be using X browser that is on version Y. Otherwise...
If people are sticking to an older browser, they will never have the functionality (this can somewhat be mitigated through transpiling using Babel, but this isn't foolproof).
It often takes years for a feature to be widely deployed enough that no tranpiling is required. There are still features from ES2015 that I think still require transpiling.
I hear this a lot, but everything can be handled with a single tool: Webpack. Even a TS project can compile with Webpack and all of its assets, minification and compression needs can be handled in the same file. While you’re at it you can set up a development server with source mapping. Just take an afternoon to learn Webpack and be done with complaining about setup overhead. I set up a multi-stage build for TS Project References but that’s only necessary if you want to share code between the front and back ends. Aside from that you should only need a single build step.
If only it was that easy. The weeks I’ve spent hunting down webpack specific bugs because the plugins don’t always quite work with typescript transformations and source maps...
If you have spent multiple weeks on a Webpack bug then you are not trying to fix it. You could have asked Stack Overflow over and over again in a matter of weeks.
There are many options for source maps due to their performance overhead and the docs describe then in detail and provide a guide for when to use each one.
And then another afternoon every week trying to figure out why Webpack is breaking this time. Many week I spend more time babysitting the environment tools than actually writing code.
The "stable Array.sort" proposal was actually made because all major browsers had switched to a stable sort implementation: https://github.com/tc39/ecma262/pull/1340
And stable does make for a better default than unstable: it offers stronger guarantees and more reliable behaviour. That's doubly important because by and large developers work to the implementation not the spec. And it's unlikely you'll ever change that.
The last one to switch was Chakra (Edge), and that one was weird as it used a stable sort up to 512 elements, and unstable above.
Eh... that's great and all, but why not go the whole way and allow me to just use `try { }` without a catch-block? I'm sure that was part of a conversation somewhere, and I wonder why they chose not to go that far.
If it doesn't make any difference if the code inside the try block is executed or not, why even have the code in the first place? What is the use case for a try without a catch?
I expect it's because of `finally`. You can write a try without a catch today, provided there's a finally, however the error will continue to propagate in that case.
try {throw new Error('')} // exception is caught
try {throw new Error('')} // uncaught exception
finally {console.log('finally')}
That would be a bit of a footgun, as adding a finally clause that does something innocuous like logging would result in an uncaught exception!
That makes sense. I guess `try` means something a bit different in JavaScript than `rescue` does in Ruby.
It seems like this code in Ruby:
```
begin
do_something
rescue => e
handle_error e
ensure
log_something
end
```
is equivalent to this in JavaScript:
```
try {
doSomething();
} catch(e) {
handleError(e);
} finally {
logSomething();
}
```
I'm not saying that they're 100% equivalent, but I was thinking of the `try` block as if it's `rescue` when really it has more in common with `begin` in Ruby in that it's defining a block of code that can be rescued/caught if an error occurs.
So I guess my confusion comes from this type of block being named `try` rather than something like `do` or `begin`.
TL;DR I was just thinking about this in the wrong way from what I can tell.
EDIT: Yet I think that my point might still stand in that, if `try` is just a block that has its own scope, like an if-block or `begin` in Ruby, then it should be possible to not require either `catch` or `finally` since its own behavior has little to do with error handling.
For instance, this is possible:
```
let x = 'foo';
try {
let x = 'bar';
} catch(err) {
// noop
}
console.log(x); // foo
```
The try-block would be way more useful if it could just define scope without being tied specifically to error handling. There are lots of circumstances where defining scope would be handy outside of conditionals and creating functions for a similar purpose.
I guess what might have made the most sense, if JS could have started over, is that there'd be no `try` statement and that `do` could be used in its place.
```
const words = ['foo', 'bar', 'baz'];
let i = 0;
do {
console.log(words[i]);
i++;
} catch(err) {
console.error('whoops!');
} while (i < words.length);
```
We can't go back now since JS, for good reasons, is remaining mostly backwards-compatible. But the ability to do the above without extra gymnastics would be pretty cool.
> The try-block would be way more useful if it could just define scope without being tied specifically to error handling. There are lots of circumstances where defining scope would be handy outside of conditionals and creating functions for a similar purpose.
This is the case today! With `let` and `const`, any {} block creates a scope. So:
let foo = 'outside';
{
let foo = 'inside';
console.log(foo); // logs 'inside'
}
console.log(foo); // logs 'outside'
These features are good (except I think Function.prototype.toString is a mistake).
(An alternative to String.prototype.trimStart would be to use String.prototype.replace, although trimStart would probably be more efficient.) Still some things missing includes: a goto command, ability to disable automatic semicolon insertion, macros, enhanced regular expressions, and a built-in regular expression quotation function (it is easily enough to implement in JavaScript, but it seem to me the kind of thing that should be built-in).
I've wished for a stable sort. But there doesn't seem to be any reliable way to determine whether the current implementation is stable or not. For most language features, you can do detection to find out whether you can use it or not. This one seems to be different.
Good stuff! Normally I cringe to see new language features, but these are pretty good (Java after lambdas, even arguably after generics, is pretty crufty, IMHO)
One thing I'd to request is that `Object.fromEntries()` simply skip undefined entries, rather than do...strange things. I wrote an `mapObject` function that looks like `(a,fn) => Object.fromEntries(Object.entries(a).map(fn))` and occasionally the fn needs to skip an entry and the easiest way to do this is just return `undefined`.
> One thing I'd to request is that `Object.fromEntries()` simply skip undefined entries, rather than do...strange things.
Not sure what strange things it does. fromEntries simply takes each entry, sets its first item as key (stringifying if necessary as object keys are necessarily strings) and the second item as value. Naturally falls from these that a null or undefined entry will error, and an empty entry will create an {undefined: undefined} item.
> I wrote an `mapObject` function that looks like `(a,fn) => Object.fromEntries(Object.entries(a).map(fn))` and occasionally the fn needs to skip an entry and the easiest way to do this is just return `undefined`.
Yes, it errors out. And flatMap isn't applicable here. Here's some complete test code you can try in your browser right now:
```
let p = (a,fn) => Object.fromEntries(Object.entries(a).map(fn))
p({a:1, b:2}, ([k,v])=> v === 2 ? undefined : [k,v])
```
My intent is to skip the entry with `b:2`. However, both map and flatMap error out. It would be nice if `fromEntries` did what I think is the appropriate thing, which is just skip undefined and null. Is this not the behavior you'd expect?
Note that without special treatment in fromEntries, there's no way for the mapping function to indicate "skip this entry". You have to do it in another pass, or some other way.
It's absolutely applicable, flatMap can trivially act as a filterMap by returning an empty array in the "remove this element" case:
let p = (a,fn) => Object.fromEntries(Object.entries(a).flatMap(fn))
p({a:1, b:2}, ([k,v])=> v === 2 ? [] : [[k,v]])
there you go.
You can even make the adaptation transparent by wrapping the fn:
let p = (a,fn) => Object.fromEntries(Object.entries(a).flatMap((v) => {
let r = fn(v);
return r == null ? [] : [r];
}));
p({a:1, b:2}, ([k,v])=> v === 2 ? undefined : [k,v])
> My intent is to skip the entry with `b:2`. However, both map and flatMap error out.
Return an empty list from flatmap to remove the entry and a singleton list containing the new pair to alter it.
> Is this not the behavior you'd expect?
No. I would much rather have the existing function which has a very clear and straightforward behaviour and is not prone to silently misbehaving on buggy code.
> Note that without special treatment in fromEntries, there's no way for the mapping function to indicate "skip this entry".
I fail to see an issue with that. If you want to remove an entry, remove the entry.
One thing I would fix about JavaScript is that currently "return" followed by a newline returns undefined. I would say that only return followed by ';' should return undefined. Else it should return the value of the expression following "return", whether on the same line or not, until the next ";".
Not sure how how much backwards compatibility this would break, but at some point we must allow for newer better less error-prone versions of the language to co-exist in different modules. That is not difficult at all. Consider the case of "use strict"; which does just such a thing. We could have "use strict 2020" etc.
122 comments
[ 3.4 ms ] story [ 166 ms ] threadLike I wonder how many bugs were introduced because the developer didn’t realize Array.sort() was unstable.
Hell, really?? That's gross.
As for the parent's comment about a dev "not realising array.sort() was unstable", the dev should know his tools. I can't see anyway why a stable sort is better, I've never sorted on x then sorted on x, you just sort on x & y.
It's just using the == operator, no? Which, yes, is really gross.
If the comparison function argument was mandatory in sort() this wouldn’t be a problem.
Stupid defaults is not "the curse of optional arguments", it's the curse of stupid defaults.
Most languages have defaults for these two operations yet have proper defaults rather than stupid ones. Python's list.sort() and int() certainly do, so do Java's Collections.sort() and Integer.parseInt().
Uniquely awful defaults is a historical (and defining) feature of javascript, not of having default values, or optional arguments.
[1,2,10].map(parseInt)
It’s several things that on their own aren’t mad which taken together produces a crazy result. 10 as the default for an optional second argument of parseInt is fine. But map should only use a single argument closure by default. So map(parseInt) must be equivalent to map(x -> parseInt(x)).
(a, b) -> a.toString().compareTo(b.toString())
Unlike say int.parse() where 10 is a reasonable default for radix, doing string comparison as default is almost never what the caller wants, so is a bad default. Functions where there is no obvious (99% of the time the desired value) should not have a default value when the argument is omitted. I’d much rather have things.sort() just fail and force me to specify the comparison, than to see it sort alphabetically.
As a side note: a second optional argument specifying stability true/false would be reasonable. True could be the default too, since unstable sorting is effectively an optimization with a tradeoff.
When neither the function nor the parameters have hard types, you have to create heuristics. There could be a test for numbers there, but it would also be surprising because at the older days people expected "10" and 10 to behave the same.
A JavaScript sort comparator is required to not only return a positive or negative value, it must handle all three cases: greater than, less than, or equal. When the arguments compare equal, the comparator must return 0.
Returning only a positive or negative value is a very common error. (I don't mean to pick on you for this! I have seen the error so often that I hope this may be helpful for anyone writing a sort comparator.)
The invalid comparator will force the normally stable sort to be unstable, because sort() now has no way to know that two items should be considered equal and their existing order preserved.
Let's demonstrate with the "doggos" example that the article links to:
https://mathiasbynens.be/demo/sort-stability
The code starts with this array:
It uses this valid comparator: (Note that this implements a reverse sort as can be seen in the output on the test page.)Subtraction provides the proper return values when two numbers are compared: any negative number, any positive number, or zero when the ratings are equal.
Another valid comparator (again using a reverse sort) would be:
Either way you get the expected output in the current version of Chrome, which has a stable sort. Each group of doggos with the same rating appear in their original order: So let's go back to the original doggos array and use a sort function that only returns 1 or -1 (once again using a reverse sort): With this function, the result is: Now the sort has become unstable. Doggos with the same rating are no longer in their original...A good alternative for statically typed languages is to have the comparison result be a proper sum type, and provide ways to generate and compose comparisons e.g. Haskell or Rust's Ordering (and Ord trait / typeclass).
You seem to think stable sort is much more important than most of the rest of the field.
By default, C sort isn't stable, C++ sort isn't stable, Ruby sort isn't stable, C# sort isn't stable, Perl sort isn't stable. Hell, not even GNU's sort utility is stable.
JavaScript has some unexpected idiosyncracies, but sort() stability isn't one of them.
I don't follow what it would use function strings for determining missing - presumably - polyfills that you couldn't do in better ways.
This of course broke in various ways with minification when the parameter names were mangled. So one had the opportunity to use "array syntax", requesting a dependency by a string. Or use a preprocessor in the build script adding the array syntax before minifying.
One thing that I am a bit afraid about though is that the language might become more complex and complicated over time because of backward compatibility (C++-like?). I saw that they will be introducing a new function "replaceAll(...)" to avoid the weird semantics of "replace(...)" (stopping after first occurrence unless a 'global' RegExp is passed as argument). And that's great, but that's a new function, and maybe 'replace(...)' should have had this behavior from the start. I hope to be wrong on this one!
New methods with simple behaviour don't really make the language more complex through.
> And that's great, but that's a new function, and maybe 'replace(...)' should have had this behavior from the start.
As you note it kinda does, just in a weird roundabout manner.
One issue with javascript is also that it makes extending existing functions difficult, because extra arguments are just ignored by default, so you can't just add a `count=1` parameter to String.replace to override the regex flag as there could be code out there which already passes a third (currently ignored) parameter and would break.
I think they do, in some ways. If you have multiple methods or functions which seem to do similar things, as a new-comer it can be incredibly confusing. I remember when I started I never knew what the "best way" to iterate on things was... for loops? for in? for of? foreach? And it was not obvious which one to use or what were the differences. I agree that it might be less ambiguous between "replace" and "replaceAll", but I think over time these things matter.
Possibly aside from the latter (assuming you mean Array#forEach), these are not different "methods with simple behaviour", they're different language constructs with largely overlapping behaviour & use cases.
The last added one is slice, and actually simplifies things, with its consistency with Array.prototype.slice, and replace the old ones
I agree it's annoying to keep old methods for backward-comptibility
For example, "move semantics" in C++ -- while useful -- trigger tons of questions about interactions with other features.
Duplicate features (for-of/forEach) are a problem, but a lesser one.
---
FWIW,
* Array.prototype.forEach iterates over an array
* for-of iterates over an iterable, including arrays
* for-in iterates over object keys (strings)
To your point, for-of is the more general form of forEach, so IMO there isn't much reason to use it now.
Ruby excels at this. There are 5 ways to do everything, but they're all simple and readable.
C++ doesn't actually need it. Rust proved that capture everything by reference or capture everything by move is enough for all practical use cases.
C++ lambdas is another example where C++ committee chose complex uber-universal solution instead of much simpler which solves 99% use cases.
C++ object lifetime management is much more tricky and error prone than the Rust one.
Passing an object by reference to lambda by default can be a beautiful source of out of scope and default.
The committee did an excellent job by allowing explicit filtering.
That's kinda true but kinda not: precise "capture clauses" is a common design pattern in rust[0] showing that the flexibility is extremely useful, and I think I've seen rumblings about adding them to the language.
I mean technically you only ever need `move` closure, "ref" closures are already a convenience.
[0] http://smallcultfollowing.com/babysteps/blog/2018/04/24/rust...
If you did have a JSON date, how would you decide what it was? Would it be a timestamp, or a civil date-time? Would it have timezones? Offsets? Locations? Would there be a database along with it required to understand it correctly?
Just stick with ISO8601 strings.
You could make the same argument with numbers - there's no reason you couldn't just pass strings containing the number - but there's advantages to being able to distinguish between a string and a number when the schema is unknown.
The reason JSON doesn't have dates is simply because JavaScript doesn't have date literals. You can write new Date(...) in JavaScript, but allowing that in JSON would have opened a whole can of worms.
Why not "just stick with" decimal strings?
Is this a joke?
JSON numbers are....I don't know what they even are. JavaScript -- the originator of JSON -- can't support 64-bit integers in JSON. Nor can it support decimal fractions in JSON, despite JSON using decimals. And yet JSON numbers also aren'ty IEEE-754 floating point numbers, as they lack +/-Infinity and NaN.
JSON numbers are ill-defined and unique from every other "number" concept in any language or format.
If you mean a 64-bit integer type (which, to be fair, is 11 bits larger than what JavaScript supports), then that's still not enough. As long as you want nanosecond-level granularity, and range beyond the current decade for your timestamps, you'll need more than 8 bytes to represent them in code.
And if you're deep enough to worry about the cost of those extra bits, you probably want to have your own date type anyway, tuned for your exact problem.
Also, forget about the receiving side, you're also assuming that the sending side meant the date for the instant as at offset 0 instead of in their own timezone (which is probably how the instant is being displayed for them). From experience, that's not a safe assumption - witness any instant meant to mean "today's date here and now" derived from a now() call alone or truncated to have 0 time parts (in current time zone!) - I see that attempted a lot. Way too error prone especially when you don't control both sides.
If you're using code that doesn't know how to handle UTC or converts it automatically to some server/env timezone, or doesn't check the timezone of the dates then that's a problem with that code, and has nothing to do with JSON or unix time.
The confusion might be coming from JavaScript incorrectly calling its instant representation Date, when it is actually quite a bit more precise than that.
Its also worth noting that you can't pick a time like 12:00 UTC and have it retain the calendar day in all time zones, because there are a few stray islands which are UTC+14 rather than UTC-10.
It does not do it automatically. Even in your example, you call `Response.json()` to do the parsing. If you change that to `Response.text()` it will be text instead. If you don't include the `.json()` call, nothing will be parsed from the body.
> Now the function would rather insert an escape character before the character code so that the result is still readable and valid UTF-8/UTF-16 code:
> JSON.stringify('\uD83D');
> // '"\\ud83d"'
I’m inclined to consider this a misfeature, unbreaking something that I’m glad was broken and should have remained broken.
Unpaired surrogates are (to simplify terminology a little) basically invalid Unicode. On the web platform, the only situation where unpaired surrogates should be encountered is as a transient state on user input, on platforms that send non-BMP characters through in two pieces (which is most browsers on Windows). Beyond that, nothing should ever deal in unpaired surrogates, because they will make things blow up and your life miserable.
Unpaired surrogates cannot be represented in UTF-8, or in well-formed UTF-16 (and that’s where JavaScript did the annoyingly bad thing that we’re paying for decades later, going with UTF-16 and not requiring well-formedness). Hence I’d quibble with the description of the result as “valid UTF-8/UTF-16 code”. (Sure, the actual JSON byte stream will be valid, but it contains an escaped string that is not valid.) U+FFFD REPLACEMENT CHARACTER was at least as valid, arguably more valid.
Various JSON parsers will choke on such strings, as observed in what I’d consider the canonical JSON spec: https://tools.ietf.org/html/rfc7159#section-8.2.
In the I-JSON restricted subset, which is what I’d say everything should actually limit itself to, such strings are disallowed and will cause parse failure: https://tools.ietf.org/html/rfc7493#section-2.1.
That is, formerly U+D83D would pass through straight as U+D83D (or possibly be replaced by U+FFFD in some implementations), whereas it is now "encoded" as the sequence U+005C U+0075 U+0044 U+0038 U+0033 U+0044.
That is, the entire point of the "well-formed stringify" proposal[1] was to stop generating broken JSON:
> Rather than return unpaired surrogate code points as single UTF-16 code units, represent them with JSON escape sequences.
[0] it's possible that some implementations would replace the lone surrogate by U+FFFD but according to MDN:
> Before this change JSON.stringify would output lone surrogates if the input contained any lone surrogates; such strings could not be encoded in valid UTF-8 or UTF-16
this is also the behaviour I observe on an old safari, and the one which is quoted in the tc39 proposal[1]:
> JSON.stringify can return strings including code points that have no representation in UTF-8 (specifically, surrogate code points U+D800 through U+DFFF). And contrary to the description of JSON.stringify, such strings are not "in UTF-16" because "isolated UTF-16 code units in the range D800₁₆..DFFF₁₆ are ill-formed" per The Unicode Standard, Version 10.0.0, Section 3.4 at definition D91 and excluded from being "in UTF-16" per definition D89.
[1] https://github.com/tc39/proposal-well-formed-stringify
The article does contain an error here, for the code point on the page is actually U+FFFD, not U+D83D (which of course is unrepresentable there). I trusted that (being a little surprised that it’d do the replacement, which is more what I’d expect of Python, but accepting it all the same) and didn’t pull out an old browser to confirm what actually happens, which is, as you say, an unpaired surrogate code point.
The article should probably say '"\ud83d"' instead of '"�"' (even though the number of backslashes still then requires thought), because yielding U+FFFD there would be a completely valid (and preferable, in my opinion) solution.
The MDN page also does that, that's a bit misleading but TFA commits way worse a sin: it actually states the unpaired codepoint is swapped for the replacement character:
> In earlier versions, these would be replaced with a special character:
which is just wrong.
> yielding U+FFFD there would be a completely valid (and preferable, in my opinion) solution.
I agree. The proposal does not explain whether U+FFFD was considered, I expect they picked the escaped version to limit or avoid data loss: if you get an U+FFFD you have no idea what it used to be.
For example `Object.fromEntries(new URLSearchParams(window.location.search))` will parse a query string into a nice JS object. This works because the iterator of the search params class returns tuples.
What is already available today is `new URLSearchParams(Object.entries(params))` for turning a "params" object into a query string.
So this is basically a fantasy what javascript could be no different than any other alt-js languages .
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
If people are sticking to an older browser, they will never have the functionality (this can somewhat be mitigated through transpiling using Babel, but this isn't foolproof).
It often takes years for a feature to be widely deployed enough that no tranpiling is required. There are still features from ES2015 that I think still require transpiling.
But overall, loving the direction JS is taking.
If only it was that easy. The weeks I’ve spent hunting down webpack specific bugs because the plugins don’t always quite work with typescript transformations and source maps...
There are many options for source maps due to their performance overhead and the docs describe then in detail and provide a guide for when to use each one.
Trying to retroactively fixing bugs in code that did not follow the standard is not a good idea IMHO.
It was actually proposed by the v8 / chrome people (https://v8.dev/features/stable-sort) after they'd finally come around to implement one of the oldest v8 feature requests: https://bugs.chromium.org/p/v8/issues/detail?id=90
And stable does make for a better default than unstable: it offers stronger guarantees and more reliable behaviour. That's doubly important because by and large developers work to the implementation not the spec. And it's unlikely you'll ever change that.
The last one to switch was Chakra (Edge), and that one was weird as it used a stable sort up to 512 elements, and unstable above.
Funnily enough, Mozilla had originally switched to a stable sort because MSIE used a stable sort: https://bugzilla.mozilla.org/show_bug.cgi?id=224128
Eh... that's great and all, but why not go the whole way and allow me to just use `try { }` without a catch-block? I'm sure that was part of a conversation somewhere, and I wonder why they chose not to go that far.
It seems like this code in Ruby:
```
begin
rescue => e ensure end```
is equivalent to this in JavaScript:
```
try {
} catch(e) { } finally { }```
I'm not saying that they're 100% equivalent, but I was thinking of the `try` block as if it's `rescue` when really it has more in common with `begin` in Ruby in that it's defining a block of code that can be rescued/caught if an error occurs.
So I guess my confusion comes from this type of block being named `try` rather than something like `do` or `begin`.
TL;DR I was just thinking about this in the wrong way from what I can tell.
EDIT: Yet I think that my point might still stand in that, if `try` is just a block that has its own scope, like an if-block or `begin` in Ruby, then it should be possible to not require either `catch` or `finally` since its own behavior has little to do with error handling.
For instance, this is possible:
```
let x = 'foo';
try {
} catch(err) { }console.log(x); // foo
```
The try-block would be way more useful if it could just define scope without being tied specifically to error handling. There are lots of circumstances where defining scope would be handy outside of conditionals and creating functions for a similar purpose.
I guess what might have made the most sense, if JS could have started over, is that there'd be no `try` statement and that `do` could be used in its place.
```
const words = ['foo', 'bar', 'baz'];
let i = 0;
do {
} catch(err) { } while (i < words.length);```
We can't go back now since JS, for good reasons, is remaining mostly backwards-compatible. But the ability to do the above without extra gymnastics would be pretty cool.
This is the case today! With `let` and `const`, any {} block creates a scope. So:
(An alternative to String.prototype.trimStart would be to use String.prototype.replace, although trimStart would probably be more efficient.) Still some things missing includes: a goto command, ability to disable automatic semicolon insertion, macros, enhanced regular expressions, and a built-in regular expression quotation function (it is easily enough to implement in JavaScript, but it seem to me the kind of thing that should be built-in).
One thing I'd to request is that `Object.fromEntries()` simply skip undefined entries, rather than do...strange things. I wrote an `mapObject` function that looks like `(a,fn) => Object.fromEntries(Object.entries(a).map(fn))` and occasionally the fn needs to skip an entry and the easiest way to do this is just return `undefined`.
Not sure what strange things it does. fromEntries simply takes each entry, sets its first item as key (stringifying if necessary as object keys are necessarily strings) and the second item as value. Naturally falls from these that a null or undefined entry will error, and an empty entry will create an {undefined: undefined} item.
> I wrote an `mapObject` function that looks like `(a,fn) => Object.fromEntries(Object.entries(a).map(fn))` and occasionally the fn needs to skip an entry and the easiest way to do this is just return `undefined`.
Use flatMap instead?
``` let p = (a,fn) => Object.fromEntries(Object.entries(a).map(fn)) p({a:1, b:2}, ([k,v])=> v === 2 ? undefined : [k,v]) ```
My intent is to skip the entry with `b:2`. However, both map and flatMap error out. It would be nice if `fromEntries` did what I think is the appropriate thing, which is just skip undefined and null. Is this not the behavior you'd expect?
Note that without special treatment in fromEntries, there's no way for the mapping function to indicate "skip this entry". You have to do it in another pass, or some other way.
It's absolutely applicable, flatMap can trivially act as a filterMap by returning an empty array in the "remove this element" case:
there you go.You can even make the adaptation transparent by wrapping the fn:
> My intent is to skip the entry with `b:2`. However, both map and flatMap error out.Return an empty list from flatmap to remove the entry and a singleton list containing the new pair to alter it.
> Is this not the behavior you'd expect?
No. I would much rather have the existing function which has a very clear and straightforward behaviour and is not prone to silently misbehaving on buggy code.
> Note that without special treatment in fromEntries, there's no way for the mapping function to indicate "skip this entry".
I fail to see an issue with that. If you want to remove an entry, remove the entry.
Not sure how how much backwards compatibility this would break, but at some point we must allow for newer better less error-prone versions of the language to co-exist in different modules. That is not difficult at all. Consider the case of "use strict"; which does just such a thing. We could have "use strict 2020" etc.
I wonder if Java will get pattern matching before JS https://medium.com/better-programming/top-5-new-features-exp...