Hacker News new | comments | ask | show | jobs | submit login
Future JavaScript: what is still missing? (2ality.com)
242 points by scottfr 20 days ago | hide | past | web | favorite | 277 comments



There are some nice ideas here, but how many different ways do you need to do something? Since JavaScript has an especially high burden for backward compatibility you can’t really remove old cruft, and adding yet another way to (whatever) just makes the language harder to learn and harder to read.

Warts and all, one of the best things about ES5 and earlier is that the language was so simple that you could pick it up in a day.

The later additions have added developer convenience for sure, but at the expense of taking something simple, consistent, and very easy to read, and turning it into a complex and subtle language with a lot of redundant syntax. For example all the different syntaxes for defining a function with the subtle differences between them. And in this articles proposal - since switch doesn’t do what I want, we’ll have both switch AND case with different semantics and syntax. Ugh.

At this point I’d rather see an effort to deprecate cruft or standardize features over throwing in new things.


There was nothing easy to read about the callback hell that resulted in the asynchronous nature of JavaScript.

Promises made it better but that was still verbose. Then async/await was added that made code read more linearly.

There was also the unintuitiveness of how var declarations were always function scoped compared to how most developers would think that variables should be blocked scoped using let and const.

Also I understand how prototypical inheritance works and I know that the class syntax is just syntactic sugar, but it is a lot more readable.


Improving some of the syntax was sorely needed but I think adding classes was unhelpful. I already have a hard enough time teaching programmers about prototypical inheritance without the language actively lying to them.

And it feels like an odd move in that class based OOP isn't the zeitgeist it once was. Shoehorning it in feels like a throw back to 90's and 00's.


> I already have a hard enough time teaching programmers about prototypical inheritance without the language actively lying to them.

One way to look at it is that you could consider not teaching the underlying prototypal inheritance model at all. If students will rarely run into code that relies on it, it may be vestigial semantics.

Think of it like how people used to teach patterns in C for doing object-oriented programming. For example, you could have a struct whose first field was a pointer to a table of functions for the operations you could perform on the object. In other words, how to implement your own vtables from scratch.

When C++ came along, it was still useful to understand vtables sometimes, but it wasn't something every programmer needed to fully internalize. C++ let them program at a higher level of abstraction.

> And it feels like an odd move in that class based OOP isn't the zeitgeist it once was.

Citation needed. :) Two of the newest, fastest growing languages are class-based: Kotlin and Swift. I'm not aware of any new prototype-based language that has noticeable growth.

Prototypes are a neat idea, but I think the market has pretty clearly shown that classes are a better practical approach to OOP.


I'm skeptical that classes make prototypal inheritance safe to ignore, because they're not a perfect abstraction. Opinion me, classes in Javascript do not act like classes in other langagues.

In particular, I still run into usage of `call` and `apply` on a regular basis.

  class Foo {
     constructor () { this.foo = 0; }
     log () { return this.foo; }
   }

  (new Foo()).log.call({ foo: 2 });
I guess I can explain the above without prototype, but it's certainly weird behavior for a classical OOP language, and at the very least it's dancing around prototype-like behavior.

Or, much worse:

  class Bar { constructor () { return new Foo(); } }
And you can say, "how often is a student ever going to run into that case", but part of being a student is experimenting with code. If an abstraction only holds as long as you never do something weird, then it's probably not a very good abstraction. In that case it's worth asking, "is it good language design to have a feature give surprising results as soon as you do something that's even slightly off of the beaten path?"


Well, in that case the abstraction of private methods isn’t good either because there are ways to bypass them in C++ by passing a function pointer or in C# via reflection.


Arguably, yes.

However, I would say there's some difference in severity between a surprising behavior that results from the intersection between two features, and a surprising behavior that results because the way a feature is described is literally inaccurate.

You can break Javascript classes without using any other feature of the language. You don't have to resort to something like Proxies, or overriding native prototypes. This is because it's not that other parts of Javascript provide escape hatches to get around behavior, it's that the class syntax itself does not cover the entirety of the prototype system it is trying to obscure.

It's not ideal to have any breakage in an abstraction, but some breaks are worse than others. To me, edge cases like this:

  let foo = function () { this.x = 5; }
  class Bar extends foo { //No error? But why?
    constructor () { super(); }
  }

  foo = function () { this.x = 7; }
  console.log((new Bar()).x); // 5, not 7 or undefined.
are on a whole nother level of leaky abstractions over even some of the worst parts of C#. To me, this is an edge case that nearly any reasonably imaginative student would likely find at some point using only very basic components of the Javascript language. And I have no idea how I would even begin to tackle explaining this without saying, "Okay, throw out everything I taught you about classes, here's how the language really works."


I don't know why prototypes have to be required for explaining those two examples. 1: You can call a function with a different `this` value. 2: A constructor can override its return value and return something else besides the new instance of its class.


> You can call a function with a different `this` value.

To understand what that means, you need to understand what `this` is in Javascript -- that classes don't actually bind methods to an instance. So we have this idea of methods from other languages that turn out to be kind of the wrong way of thinking about things.

From `this`, the obvious next question is, "so what's the difference between adding a method to a class and just attaching it to an object?" And it turns out that practically, the difference is very small -- and in fact it's very easy for us to remove or add methods to a class that look and act very similarly to the methods in a class descriptor. So what we're calling a "class" is no longer a descriptor or a set of laws about what an instance is and what it can do, it's just a blueprint of how to build an instance at runtime.

We haven't gotten rid of classes yet, but we're already working with something that feels foreign to a number of programmers coming out of traditional OOP languages. `this` means that what a traditional OOP programmer from Java thinks of as a "method" doesn't exist. It means that the context of a function is determined at runtime, not at compile time.

You don't need to use the word "prototype" to talk about that, but it's what I would call very prototype-ish behavior. Arguably, by the time someone understands `this`, they've already learned about half of what they need to know to grok prototypes anyway -- all that's left is to tell them that if a property isn't found on an object, there's a special property JS will traverse to look for it elsewhere. But sure, we can delay talking about that for right now.

> A constructor can override its return value and return something else besides the new instance of its class.

Constructors complicate things more. We already know from above that a class isn't really a descriptor of what an object can do, instead we're thinking about it like a blueprint that tells us how to create an object at runtime (this is also incorrect, but we're sticking with that definition because we don't want to use the word "prototype"). Now we find out we can override the return value of a constructor. The obvious question a good student will ask is, "why? What use-case is there for treating a constructor function like a normal function?"

Again, we don't have to jump to prototype from here, but we do kind of need to explain that the `new` keyword is actually doing most of the work when we build a class, and the reason we can do weird things with a constructor is because a constructor is just another function. But we'll avoid prototype by saying that `new` just makes an instance of a class, and that behavior can be overridden by returning another object from the constructor that `new` invokes.

And when the student figures out that they can call `new` on an anonymous function without returning anything and it still creates an object, we can delay talking about prototype even farther, because we'll just lie and say that pure functions are a shorthand for making classes that are only a constructor.

But increasingly the question becomes, why wouldn't we talk about prototype now? Ostensibly, the point of classes is that it hides all the weirdness of the prototype system. In my experience, people struggle with prototypes because they're not Java. But JS classes also aren't Java, or at least they stop being Java as soon as you do anything even mildly interesting with them. So if people who learn classes in depth still have to grapple with the fact that class definitions are constructed at runtime, and that instance methods can be modified after they're created, and that methods aren't inherently bound to instances at all, what have we gained?


I agree that prototypes come into play if you start asking "why did they add this feature?" to either of those questions, or if you want to explain why JS behaves the way it does if you go off the paved path ("And when the student figures out that they can call `new` on an anonymous function without returning anything and it still creates an object..."). I think I disagree that either of those are on the critical path to becoming productive with a language.

I just think people really over-emphasize the difference between "class-based" and "prototype-based" languages as if they're completely different in how users code with them. In my experience, the primary interesting consequences of JS's prototype-basedness vs class-based languages is that you can call methods with arbitrary `this` values, monkey-patch classes at runtime, or even change the class of an object at runtime. In other languages, these kinds of features would be called reflection, they would be in a late chapter labeled "advanced features", and people wouldn't worry about teaching them to students before they had a good handle on the rest of the language.


I do not use the "class" command in JavaScript (although I know it just does prototype inheritance), and think it was unnecessary to add (but you can't really remove it now).

I think prototype inheritance is often more useful than class inheritance; I do not agree that classes are necessarily better. Prototype inheritance can be used for more kind of things (especially with Object.create and Object.getPrototypeOf; I sometimes use these).


I don’t disagree that prototype is rare in real world practice but, anecdotally, it has come up in ~20% of my interviews in some fashion. That’s enough that I would be upset if the person teaching me JavaScript didn’t also teach me prototypal inheritance.


I only bring up the prototype to determine if I would classify the interviewee as a senior JavaScript developer or not.


In modern times that's something you can easily throw far back in mindbank, because in practice using ES6 it almost never comes up, and it's super easy to do a quick refresher on the semantics. If I didn't know that interviewers tended to ask about it in frontend inteviews (because they totally do), would never bother to keep up to date with the semantics


Object.defineProperty('senior', { value: true, enumerable: false })


There are two things I would expect even a mid level developer to know - how prototypical inheritance works and how “this” works in JS.


The two most popular statically typed OOP languages for servers are still C# and Java. Besides JavaScript, the most popular scripting language is Python (let’s ignore PHP for now) which is class based.

In the mobile space, Swift, Java, and now Kotlin are all class based.

“The zeitgeist” is different than “what companies are paying real money for”.

Besides, even with OOP based inheritance, the language is still “lying” just as much. Most developers who don’t understand how the lowest level of computers work don’t understand that when you call a function it’s always basically passing in a this pointer to one static instance of the function.


Object orientation in the sense of having some sort of data structure having some officially associated "methods" with it, and then building other structure on top of that, is alive, well, and not going anywhere any time soon despite the occasional functional programmer epiphanies.

Object orientation as in "There must be classes, there must be inheritance, each method and instance value must be able to be private, public, or protected, and have official constructors and destructors" and probably a couple of other things I'm missing, is also alive and well and not going anywhere any time soon, but is no longer the only definition of Object Orientation.

If you were not around at the time, you'll have to take my word for this, although the echos still reverberate in Javascript tutorials to this day (because of the way they talk about prototype-based inheritance as if you've got to be placated long enough about the fact that it isn't "true" OO long enough to learn what it actually is), but when it was released, Javascript (prior to any sort of "class" keyword) was not considered an "object oriented" language, because it didn't have "real" inheritance, or private values, etc. etc.

The point is that in a world where the definition of "OO" has expanded enough to encompass the original JS, it's a bit odd to bodge on a particular definition of OO so late in the process.

(People also argued back then about whether Python was object oriented (!), because it didn't have "true" support for private instances or methods. By contrast, I find it amusing when sometimes the question comes up "Is Go really Object Oriented if it has no inheritance?" and the answer generally amounts to "Honestly, who cares?")


Modern PHP is class based, the on going ignorance of PHP continues to insulate their own bubble


JS classes look like classes, behave like classes, and for all practical purposes are classes. They were one of the most useful additions to JS in recent years and make code so much more readable compared to hacking things together with the prototype system. With the added bonus, that you can still modify the prototype of a class to replace functions for all already generated instances of that class.


Don't disagree, but for React users onClick={this.doStuff.bind(this)} is pretty common for newbies who just read it somewhere and don't know of the perf implications of bind.

But personally I prefer prototypal inheritance and the everything is an object mentality.


> without the language actively lying to them

What does class mean?


In what way does it lie?


There are no classes, just functions. 'class {}' is a function. Try it.


I’m going to butcher this. But, in the grand scheme of things a “class” in most languages is just a group of related static functions. When you create an object from a class definition you are creating an object that holds values. When you call a method on that object, you are passing a pointer to the object to a static function.

It’s all just syntactic sugar in any language. Before the class keyword, it was just more explicit in JS.

A class is a function in JS, a class is a separate type in most other languages. I don’t see a meaningful difference.


> a class in most languages is just a group of related static functions.

In class oriented languages like C# and Java, sure. JS with ES modules has the alternative of using functions exported directly from a module.

The latter approach has the benefit of better tree shaking than the class based equivalent.


Would your definition classify most of lisp as a lie? It being implemented with a function shouldn't matter. What about the actual behavior is wrong for a class?


It's not implemented with a function. It literally is a function (object constructor).

class a {}

typeof a == 'function'

Objects in JavaScript are not class based. You can read here about the diferences:

http://www.ecma-international.org/ecma-262/6.0/#sec-objects


There is no 'class' type. That doesn't make classes fake, any more than a linked list or B-tree is 'fake' in C.

As that link says, objects are not fundamentally class-based because objects don't have to have a class. But you could add classless objects to almost any language without making their classes fake. And when it talks about inheritance, well, you can inherit state in C++ too, it's called 'static'.

Or in shorter form: It's not class-based but it does have classes.


There are no integers in Lambda calculus, only natural numbers Church numerals built out of functions. That doesn't make numeric support a "lie" in Lambda Calculus.


I'm not saying classes are fake. There are no classes in the JS runtime. It's all just objects and some property sharing via a prototype chain.

You can implement anything in any general purpose language. But that doesn't mean the language has that concept.


The language has the keyword and the implementation.

It's pretty normal for runtimes to not know about certain language concepts. The C runtime has never heard of switches or while loops, no big deal. If it's in the compiler it's part of the language.


Objects don't have a class in JavaScript. It's that simple. You'll not change that with word games.


You're missing the forest for the trees. A fixed and reusable prototype, with a constructor, is a class. And when that's part of the language spec, that means the language has classes.

And it doesn't matter what typeof says. I could take a C++ program, replace every instance of the word 'class' with 'struct', and it would work fine. It would still be using classes, even though typeof says 'struct'.

Typeof in javascript calls the constructor a function and it calls objects objects. That's correct and fine. You can't put a class itself into a variable in C++ anyway.

And if you really want to get into the gritty details, it's possible to make a very similar argument that C++ of all languages doesn't have classes. Sure they exist in the source code, but all that pretty syntax gets thrown away and you end up with a naked object or one that just has a pointer to a struct it inherits from. And there's not even a 1:1 relationship between source-code 'classes' and those structs!


> You're missing the forest for the trees. A fixed and reusable prototype, with a constructor, is a class. And when that's part of the language spec, that means the language has classes.

Yes, but prototype is not fixed. Prototype chain is not fixed (you can extend it at any point). All of that is changeable at runtime. You can also change the object's prototype at runtime.

It's all dynamic, and prototype ("class" as you say) doesn't provide any structure/constraints to the object. Thus it's pretty pointless to call it a class. It's just property sharing via a prototype chain. It's nothing like classes in languages like C++, Java, PHP, etc. And it doesn't help anyone understand the language, if you try to see it through that paradigm.


We are now in an intermediate hell of Babel and webpack though...the language is cool, but the toolchains are really crazy right now. Someone could dismiss my argument if they got a comfy setup but often times if you're doing even just a little trailblazing you get into some wtf territory. I spent all day this week setting up a karma test cause my node module was failing only in the browser so then I added Babel and karma-webpack, but then regenerator runtime kept giving me errors, then mixing require() and es6 import gave me insanity errors and I almost gave up. Deep googling gave me barely enough clues to get through it...


I’ve avoided any serious JavaScript development for years because of crap like this except when absolutely necessary. I’ve learned it well enough to get pass “full stack developer” interviews, but as soon as I get hired, I get migrated to the back end.

I’m just now putting the finishing touches on my first Node/Express microservice for production. I’m putting off front end development for another year or so.


I agree that the class keyword is far more readable. I never find myself using them in the "classical" sense, though I could see how it would be tempting for someone coming from, say, Java.


You could put callback back in the bag https://github.com/staltz/callbag-basics


It reminds me a lot of what happened with C++ in the past few years, and in contrast to C which has basically stayed still meanwhile (I know there's C99 and C11, but a lot of code out there is still C89 --- and IMHO that's the way it should be.)

I am very much against constant change in a fundamental platform. I should be able to write JS that works for decades without worrying about deprecation or any other sort of useless "churn". The less the platform changes, the more you can focus on understanding it and doing more. But of course it seems the majority of developers would rather have "new and shiny" over "old and stable", which I think is quite unfortunate.

The old saying is "do more with less". What's happening now in development, especially Web, seems to be more like "do less with more".


JS was born a horrible language. It needs to be fixed before it becomes so entrenched that you might write code in it that has to work for decades.


I've never thought of ES5 as simple, consistent, and easy to read. There are plenty of features that many devs wouldn't recognize (ie, "the bad parts"). They've fallen out of vogue because there are better ways to do things, but they're still part of the language. I, too, would love a simplification of the language, but as you mention, backwards compat is necessary.

And what does `case` not do that you want it to? Fall-through? Re: forced destructuring, if you want to switch on a value, why not just wrap it in an object, eg, `case({ someVal })`? It's not perfect, but I'd prefer consistently using `case` to the confusion you call out re: `case` vs `switch`.


The point is that case does not currently exist, but switch does, and is fine. If we could get rid of switch and replace it with this case proposal, that might be nice, but we can’t. Having the language with both existing side by side is, to me, worse than just living with switch.


> Since JavaScript has an especially high burden for backward compatibility you can’t really remove old cruft

You can't remove features from JS runtimes (browsers or node), but you can certainly remove them from a new language version. "use strict" already did it once in JS. It can be replaced/upgraded with "use ecmascriptX.Y" for each language version.

And transpilers targeting JS and tools like packers and minifiers can enforce language versions without declaring them in each file.


"use strict" was also highly contentious for a time. "use versionNumber" would have similar issues to the battles the web fought over DOCTYPES and "quirks" modes and browser compatibility.


ES5 took me months to understand well. And today, after years (and now on ES6/TypeScript) I cannot always properly describe some features of ES5 without seriously reviewing them.


there was nothing easy about var hoisting. modern javascript with a good linter is a lot cleaner and easier to read.


I'd like a decent standard library.

These days I work mainly with C#, and to a lesser extent Typescript and JavaScript - the anemic nature of the ECMAScript standard library is really jarring.


I mostly work with Java and Python. Whenever I use Javascript, the lack of basic data structures other that arrays and objects is always jarring.


Coming from clojure, I find that it’s not data structures that JS lacks, just functions/methods to manipulate them and a consistent approach to their mutability and return values.


Objects, Arrays/lists (technically objects), TypedArrays, Set, Map (with weak variants). What other important structures did you have in mind?


ArrayDeque, LinkedHashMap, TreeMap, PriorityQueue, even LinkedList. Their interfaces have gotten pretty built-out, too, with methods like computeIfAbsent.

It also has a rich collection of concurrent data structures, but those aren't useful in Javascript (yet).


Lucky for you, JS already has good solutions for most of those things.

LinkedList is almost indistinguishable from an growable array. Since the JS array grows by default, you're covered for the kinds of things you'd want a linked list for (in fact, most older JS implementations actually implemented arrays as linked lists).

ArrayDeque's big claim to fame is add/remove from both ends. That's also covered by the standard array (shift/unshift, pop/push).

Linked hashmap's big feature is maintaining insertion order. The JS Map type does this automatically. JS Objects don't do this officially, but since so much legacy code depends on this "feature", it is implemented for objects across all major browsers.

Priority Queue isn't in JS, but I'd argue it's rather niche and rather few async frontend JS projects need it. On the backend, one extra import is hardly a big deal.

There is also no treemap. They are simple to implement (esp with proxies). JS objects store all keys as strings and `Object.keys(foo).sort()` is probably about as fast. As an additional issue, JS maps can have keys of different types, so deciding how to perform that sort would be very difficult. This also seems like a niche issue that belongs outside the standard library.


> LinkedList is almost indistinguishable from an growable array. Since the JS array grows by default, you're covered for the kinds of things you'd want a linked list for (in fact, most older JS implementations actually implemented arrays as linked lists).

Not only they have different performance for various operations (append, prepend, indexing, removal in the middle), but perhaps even more importantly, linked lists can share data if it's immutable. So an array is not a replacement.


You'd be correct -- if we were talking about a lower-level language.

JS implementations are free to choose whichever array representation works best be that hashtable, struct, class, linked list, array, or whatever. Modern JITs use a couple different approaches that have been found to be fastest and can even switch representations on the fly based on several factors (is the array homogeneous, is it sparse, is it short, med, or long, and so on).

Even moving away from JS, there should be a healthy skepticism for basic O notation about data structures and algorithms. When your processor is thousands or millions of times faster than your ability to send over data from memory, the notion of what is faster changes a lot. If my linked list misses in cache because it's memory isn't sequential, then the time it takes to add an element and read may be much longer than copying an array and expanding it a few entries (especially if a fill pointer is used).


Are you saying that JS implementations somehow know whether the array is used to store immutable values, and will switch implementations to allow for sharing?


In JS, primitives are immutable -- number, functions (but not the fn object), regex, strings, and booleans. All other types are not immutable -- Object, Array (actually an Object), Map, Set, TypedArray, etc. Even if a linked list were added, it would not be immutable.

That said, JS has Object.freeze() which can make an object immutable. You could do this recursively to make a guaranteed immutable object. Immutable libraries in JS tend to use other methods for performance reasons though IIRC.


There is also Map and Set.


This! So much this. JavaScript-the-language is in decent shape now, but JavaScript-the-ecosystem is a complete and utter mess. Having even a small standard library would go a long way towards cleaning that up.


Yes. TFA mentions it in passing, but to me JS’s main issue is not the language but the ecosystem. You can’t install an npm package without inheriting hundreds of dependencies. Reading a typical package-lock.json file makes me cringe. I feel that a fully-fledged standard library (python’s comes to mind) would help a lot in that regard.


JS cannot simply release a standard library and every browser will autoupdate to have it. It takes browsers years to get up to date with the latest in ecmascript. So any solution would look at 5 years before adoption. This is why transpilers have taken over: polyfill and shim all the things. It will still add bloat to js bundles, it will still require npm packages.

This is the world we live in and it is unique to javascript.


The best time to add a decent standard library was 10 years ago. The second best is today.

Shipping a decent set of core libraries would do wonders for package sizes. You could cover a lot of use cases with a relatively small API surface area (the classic 80/20 rule) and you'd find that a lot of duplication in implementing the fundamentals in third party packages would just go away.

Browsers update pretty quickly now, anyway. It's likely that in the future we will see most sites using a lightweight package for modern browsers and a heavy polyfill for IE, which is fine by me.


And yet ES6 exists. If the community wanted, it could do the same with the standard library.


I may have overlooked it, but I want actual proper integers. Not doubles, just integers.

I also missed the awesome bind-operator. EG:

    const getFoo = () => this.foo;
    const bar = { foo: 1 };
    console.log(bar::getFoo()); // 1
https://github.com/tc39/proposal-bind-operator


https://github.com/tc39/proposal-bigint is shipping in v8 (chrome and node) and firefox is about to ship


Just hung Chrome tab by running "2122n 12122n" in console. Had to use task manager to kill it.

Keep in mind `this` is not scoped to arrow functions. So even if the syntax was added the way you proposed, this code wouldn't work or make sense.


You could use the same technique as asm.js for acquiring and handling integers. Unary `+` always produces a Number or an exception. Bitwise operators treat their operands as a sequence of 32 bits, so a bitwise OR with 0 will cast the value to a 32-bit integer:

    +value|0
There's also TypedArray [0]. I think you'll be interested in the BigInt proposal [1] [2], which is stage 3. It's already available in Chrome, and behind a flag in Firefox. Now that it's possible to accurately represent 64-bit values, we're getting BigInt64Array and BigUint64Array.

[0] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...

[1] https://github.com/tc39/proposal-bigint

[2] http://2ality.com/2017/03/es-integer.html


This code wouldn't execute properly, since arrow functions are scoped to the surrounding code block and cannot be rebound.

You would need to use `function` for this:

  function getFoo() {
    return this.foo;
  }


You're right! I forgot about that

I have a feeling I'm walking into a very dumb question/obvious answer, but here goes.

If you're putting getFoo at whatever level of scope that you'd run it over multiple bar objects that don't have it bound, why is it conceptually different than writing it as

  getFooOfBar(this_bar:hasfoo) => this_bar.foo
(If you get my drift from the terrible notation; and you could drop the type-safety and just take an any obj if we're being more literal to your pseudocode)

I had honestly not seen the bind operator used in at least the last decade, so perhaps the dissonance is just whatever corner of tech I work in having a distinct style/idioms.


> I want actual proper integers. Not doubles, just integers.

Why exactly do you want that? Is it arbitrary precision you want? Is the 56 bits or whatever not enough for your use-case? Or do you find the range checks are too expensive in practice?


Yes 56 bits is not enough. I want compatibility with database types and any other protocols I choose to use rather than having a js-crippled subset.


53


Being able to have a constant that is not constant makes me feel like i am being played for a fool somehow.


Constants are not deeply immutable in other languages either.


There's some interesting stuff in here, but I'm noticing a large chunk of it is syntactic sugar.

Keeping the extensible web manifesto in mind, I wonder if maybe what we really want is to have some kind of standardization around transpiling code.

With new features like promises, we had extensive testing via user libraries before they made their way into the language. But with these kinds of syntactic features, that doesn't seem to be happening.

If a feature is almost purely syntactic sugar, maybe it would make more sense for it to be supplied via a transpiler rather than being baked into the core language? Are there pain points or disadvantages to libraries like Babel that we could address via the core language instead?


>Are there pain points or disadvantages to libraries like Babel that we could address via the core language instead?

I think the main disadvantage is that compilers need to target something, and then JS engines need to be able to optimize that code even after it's transpiled. That's something that bringing these features into the language itself can help.

Take a look at async/await for an example of this. For a fairly long time it was extremely slow to run async/await when it was compiled to es5, because the subtleties required some fancy work on the part of the compilers, even going so far as to include a small runtime to achieve it.

Once it was supported by the JS engines themselves, it pretty quickly matched the speed of code full of `.then` with promises.

It's obviously not impossible, but I'm guessing it's much harder. There's a reason why the x86 architecture is still getting new features and additions, and it's not just for the comfort of people writing raw assembly.

Then you need to think about other tooling. Linters, compilers, static analyzers, minifiers, they all need a way to analyze the code you are writing. If that code is using features that aren't "standard" (or are on track to become standard maybe), then it would quickly get much messier than it already is.

You aren't just talking about compiling now, but also a unified way of adding "plugins" to linters, compilers, static analysis tools, syntax highlighters, minifiers, and more. Because adding a "syntax plugin" is a non-starter if it means we can't minify our code or run our linter, and creating a new "syntax plugin" would be much harder if you also had to make plugins and changes to all of those things to even let it begin to become useful.

Adding this stuff to the language is where it belongs, things like types on the other hand I firmly believe belong in "compile to JS" languages or dialects like TypeScript or Flow. That's a great abstraction level for that, simply because it doesn't change the output of the code (for the most part), it just enforces restrictions on it.


Yes it makes the toolchain more complicated and it makes debugging slightly harder. On top of that having it as a standard means you can go anywhere and just use it without worrying about which transpiler they are using.


> Yes it makes the toolchain more complicated and it makes debugging slightly harder.

Are there ways we can help with this? Improvements we could be making to sourcemaps? Maybe we should be looking into adding new descriptors that are designed specifically to help generated code get read by debuggers? Maybe some kind of support for marking regions of code so you can run multiple transpilers on the same codebase?

I dislike transpilation in its current form, but I can think of things off the top of my head that would make it easier.

> On top of that having as a standard means you can go anywhere and just use it without worrying about which transpiler they are using.

Fair enough, there are definitely advantages to having a large set of core features. But if you look at languages like modern C++, it seems that there are also disadvantages that come along with that.

Given that the web is far more permanent than most other languages (we can never remove a JS feature), are the advantages of having one code style worth the disadvantages of not being able to test new syntax in the wild before we standardize?

There's a balance here -- I like lodash, a lot. But I don't want lodash to be part of the core language. Maybe some syntax fits into that category as well?


But why not have a larger standard library for Node for instance so you don’t have so many dependencies and have separate optional libraries for the browser?


Because https://medium.com/the-node-js-collection/keeping-the-node-j...

Note that Node is actually in a much better position than Javascript as a whole, because Node apps can be shipped with a specific version of Node. This means that the Node team actually has more power than web browsers to deprecate old features and remove parts of the library, as long as they do so carefully. It is much harder to make breaking changes in a browser than it is to make them in Node.

One middle ground I've seen people suggest is to have officially "blessed" external libraries that still need to be imported, but that are cached locally or have special privileges. For example, JQuery was at one point so common that the Firefox dev tools actually ship with a subset of the JQuery API enabled -- just so devs that are used to working with it can use it in the debugger on any site.

I look at JQuery as a massive success story for how 3rd-party extensions should work. It was extremely ubiquitous, it did influence the core language, but now we've grown and because it was kept separate, we're still free to abandon it and move on.

On the Node side maybe that means that Node ships with dependencies that still need to be added to your package.json, but that are always bundled in your installation so that you have them locally and don't need to hit a network request when you type `npm install X`.

Also on the Node side we have modules that are built by the core team, but just not distributed in the core library. This helps out a bit with security, because you can still have a large organization like Google maintaining a feature and validating it, they just ship it separately from their browser.


As someone who teaches JS on the side, I'm literally scared of the idea of introducing another construct called "case." Especially one that goes in the opposite place of where the "case" normally does: "switch ... case" vs. "case ... when."

JavaScript is already jam packed with subtleties and gotchas that make it hard to learn, including situations where two things have the same name or the name doesn't quite match the underlying concept: C-style "for" vs. "for ... in" vs. "for ... of," object vs. Object, Object.prototype vs. "the prototype of the object," etc. We don't need another one of those.

I'd actually like this if it used another word than "case." "choose ... when", maybe? Kinda awkward, but I bet someone could think of something better. "case" has the nice feature that it's already a reserved word, but it's not valid syntax to have a variable name right next to an open brace, so the parsers could figure it out.


Is it just me or are those really making Javascript weirder?

    const func = do {
      let result;
      () => {
        if (result === undefined) {
          result = someComputation();
        }
        return result;
      };
    };
Shouldn't that be "if (result === undefined) then someComputation else result"? Why import functional style and proceed to make it inherently imperative with a return in the middle of the block?

    obj?.foo?.bar?.baz
The point of optional chaining is that you can place functions within the chain. That seems to be a very surprising and useless way to do them.


This is the equivalent without a do expression.

    const func = (() => {
      let result; // cache
      return () => {
        if (result === undefined) {
          result = someComputation();
        }
        return result;
      }
    })();
You could write it without the immediately invoked arrow funciton or a do expression if `result` is in the same scope as `func`

    let result;
    const func = () => {
      if (result === undefined) {
        result = someComputation();
      } 
      return result;
    }
Result is in the same scope as func, inviting anyone to read from or write to it.

In all three examples `func` is assigned a zero argument function. That function returns the result of calling `someComputation`, but it stores the value in `result`. If `result` is not undefined `func` returns `result`. `func` is a cached, memoized, or lazy-loaded version of someComputation.

A do expression will return the last evaluated expression in it's scope, in this example that expression is `() => {...}`, not `return` because the inner anonymous function wasn't called.

---

I think a clearer demonstration of do expressions is an assigning switch or ifelse

    const value = do {
      switch (someString) {
        case 'Monday':
          0;
          break;
        case 'Tuesday':
          1;
          break;
        ...
      }
    }

    const other = do {
      if (isPrime(n)) {
        'prime'
      } else if (isOdd(n)) {
        'odd'
      } else {
        'even'
      }
    }


Oh, your examples are better. Except for the break on the functional switch (that is weird again), they are how it should be done.

Is the example on the article wrong? A functional "if" shouldn't accept just one side of the branch, a functional switch shouldn't accept a "break", and although it's not absurd that a functional code block accepts a "return", it is not usual.


the break is part of a switch/case though?

https://www.w3schools.com/js/js_switch.asp


The switch example seems odd to me, since in that case getting rid of the `do` expression and using returns turns out to be shorter than futzing around with breaks.

  const value = (() => {
   switch (someString) {
     case 'Monday':
       return 0;
     case 'Tuesday':
       return 1;
     ...
    }
  })();


To me this is a code smell, the function would be better off being a named function like getStatusString(n)

    function getStatusString(n) {
      if(isPrime(n)) {
        return 'prime';
      } 
      if(isOdd(n)) {
        return 'odd';
      }
      return 'even';
    } 
Much easier to read, understand and maintain than an inline monster. No need to add complicated new language constructs for simple situations.....


I agree, these are contrived examples, but I know that I have reached for inline logic many times in my career. To me another code smell is a scope with too many variables and do expressions are a tool to reduce the scope of variables. Refactoring to named functions will introduce another named variables.

Javascript doesn't have private modules so moving logic to helper functions in util modules doesn't reduce scope bloat.


I really miss the simpler times of ES3/5.

    function memoize = function (fn) {
        var value
        return function() {
            return value !== undefined ? value : (value = fn())
        }
    }

    var func = memoize(someComputation)
Why would anyone want to write that do/iife abomination inline?


It's lazy loading - someComputation() is only called if func() is called, and called only once. Perhaps you might prefer: result || (result = someComputation())


I did really like optional chaining in Ruby. Never used it for functions, just for unpacking deep objects as per the example.


Javascript is still missing simplicity, consistency and a unified core set of ideas. It will always be that way too. Javascript will always be something grudgingly accepted or used with some misgiving. ES6 has some very nice features and the good parts of javascript stand out as much as the unfortunate inconsistencies lurking inside the language. People know how to use it. Some use it quite well but I always get the feeling I have to step around carefully and not make a dumb mistake in js which is easier to do than almost any other language except say PHP. Web assembly should open up some badly needed possibilities. All we really need is a consistent API. A world without js? I hope so.


I feel like this opinion is too popular so I just want to respectfully speak my peace. I'm a relatively new developer and I never learned any of the languages that a lot of the old guard fawns over, but after learning coding with Racket, Java, and Python, JavaScript (especially TypeScript) has always felt, and continues to feel, awesome.


I agree with @jdpigeon

People who come from other languages often make “dumb mistakes” because they try to apply their existing mental models of programming to a language that works differently.


I don't know what languages you're referring to but I didn't have my eyes opened until I spent significant amounts of time learning Haskell. I think any language with suitably advanced type system would do: ocaml/reason, rust, swift, etc. All the OO c-derived languages feel super bland to me since they all deliver equivalent expressive power, more or less. You get a really different experience once you get a language with a more capable type system.

Honestly I'm kind of surprised that Racket didn't stand out to you, it's a super cool language and very different from everything else on your list. How much time have you spent using it?


Haskell is probably #1 for languages that old guard types fawn over and, yea, I think it's pretty cool, but don't ever expect to be in a situation where I get paid to write it.

I really liked Racket a lot actually! It turned me on to the whole functional programming style that I still try to maintain in JavaScript. Unfortunately, I only got to write it for a semester in school and when I moved on to JavaScript enjoyed not having the insanity of all those parentheses and not having to do everything myself (via js array functions, lodash, and the npm ecosystem)


There was a time when I never thought I'd get paid to write unit tests. In fact, I often joked about how much I wanted to write tests but that it was completely unrealistic to expect the industry to change. When my boss suddenly jumped on board the testing bandwagon I thought, "I'd better not lose this job. I'll never find another one like it". Things do change!


Maybe there's a reason people keep saying it? I guess you'll never know until you try it yourself.

> enjoyed not having the insanity of all those commas and not having to do everything myself

Racket has a pretty robust std library that includes things like folds, maps, zips and whatever else you'd be using from lodash. It's also pretty void of commas, not really sure what you mean.


commas -> parentheses


IMO you should learn one functional language well and then you'll have a good contrast to compare against.


> Javascript will always be something grudgingly accepted or used with some misgiving.

Always? I hope you're only speaking for yourself? Lots of people come to JavaScript from another language, never put in the time to learn JavaScript, and then just complain about it. That used to be me. And then I committed myself to learning the language really well. And now I enjoy using it. I know lots of programmers who also love it. I've used a lot of languages but I don't think I'm qualified to say which is the "best". But I do know that I really like JS.


I was tempted to cite Antoine de Saint-Exupéry's quote on perfection, but we all know that, right?

But then again, even the author mentions at the beginning the "risk of adding too much" and, well, I wonder if this has not already happened to JavaScript.


To be fair, since JS needs to be so backwards compatible, it's difficult to take things away.


JS is a language where new features can only be added by changing the language itself. There are other languages where new features can be implemented as third-party libraries. This allows people to experiment with new approaches to problems, try out new features and test them in real world. Once the community agrees on their usefulness, they are integrated into the standard library so everyone can profit. These are languages where you can't really remove that much, from the language itself. And any new language features (changes to the core language itself) are driven by "what is the minimum change we need to do to allow third-party code to grow even better" rather than … well, whatever that thing is that the JavaScript community is doing.


I feel like there's something I'm missing with do expressions. Are they purely syntactic sugar? What can I do with a do expression that I can't do with an immediately invoked anonymous function?

The examples the article gives don't really look all that syntactically different to me.

  const func = (() => {
    let result; // cache
    return () => {
      if (result === undefined) {
        result = someComputation();
      }
      return result;
    }
  })();

  const func = do {
    let result;
    () => {
      if (result === undefined) {
        result = someComputation();
      }
      return result;
    };
  };
And I guess I wouldn't be able to recurse in a do expression like I can with a named function? So it's not like I can actually universally stop using immediately invoked expressions everywhere.


Syntactic sugar I not a bad thing. In any sufficiently complex language, most features can be simulated with other primitives. If we didn't want that, we'd all be writing lisp.

In this specific case, the IIF is harder to read. You have to go all the way to the end of the expression in order to realize intent. With 'do', you see it right away.

Like anything in engineering, it's a question of trade-offs. Do the benefits of better readability outweigh the downside of making the language more complex?


They also inherit break/continue/yield/await/return/etc, which an iife will cut off.


You’re not missing anything, as shown in the article it’s just sugar.


A lot of great and well-thought-out suggestions here. Whenever taking deeper dives into Javascript, I tend to find myself on 2ality.com though google searches and whatnot. They do well when it comes to breaking down the language and features.

I'm a fan of 2.2: destructuring switch, although I occasionally employ something similar in JavaScript and other languages with a `switch` statement:

    const resource = await fetch(jsonService);
    switch (true) {
        case resource.status === 200:
            console.log(`size is ${resource.headers['Content-Length']}`);
            break;

        case resource.status === 404:
            console.log('JSON not found');
            break;

        case resource.status >= 400:
            throw new RequestError(res);
            break;

    }
It's missing the destructuring aspect, so the first case is a bit more verbose, and I imagine there's better potential to optimize something like the OP's example. But it has a similar effect to the example and works today.

Slightly related: I really wish `case` worked with brackets instead of break, similar to OP's example. It looks cleaner and is easier to visually parse. I suppose that's only better if you prefer brackets (which I do).


I would like a null conditional operator and null coalescing operator.

return data?.error?.message ?? "no error";

Also pattern matching would be a big win.


Absolutely this. I've done work in both Swift and JS (well, Typescript) and every feature on my wishlist is from Swift. Null operator like you suggest, guard statements and structs, which are the best equivalent to value-type objects as outlined in the article.


Yep. Both are stage-1 proposals:

https://github.com/tc39/proposal-optional-chaining

https://github.com/tc39/proposal-nullish-coalescing

Recently started using optional chaining in our codebase (via Babel 7) and it's great so far. Also gives us proper static typing safety unlike Lodash `get(...)`


Null coalescing is already possible with ||. Though I'd like ?? If it worked for undefined coalescing as well.


How does || differentiate null from e.g. 0 or ""?


It doesn't - it tests for a "truthy" value.


These are not the same.

    let val = 0
    val || 30 // 30
    
    val ?? 30 // 0
The same goes for other falsy values.


You still have to be more verbose, though. You can't do obj1?.obj2 with ||, not could you do obj1?.method()


What's really missing from javascript?

1. The ability to run my code without transpiling and reinstall dependencies for 10 minutes.

2. The ability to know which of the last 5 version of javascript to use.

3. Stability in my dependencies.

4. Consolidation and standardizing on established frameworks.

Basically undoing all the mess the ecosystem has done over the last 8 years.


1. Stop supporting IE11. 2. Whatever has 99% support in browsers your users actually use. 3. Yes. 4. It’s called React.


4. I don't actively javascript anymore (for the reasons listed above), but isn't there still a bunch of variation within that (e.g. something called "Vue" and something called "redux")? Does google use this "react" ?

In most traditional languages if you learn a framework you'll get a decade of use out if it. ROR was the first hacker community to really screw this up.


Redux is a nearly-ubiquitous complement to React, not an alternative. Redux handles the data, React handles the interface.

Vue got a lot of attention on Hacker News because it was new, but it has plateaued with at most 1/8th of React's market share. It is not going to be replacing it. React is more dominant than any JavaScript technology has ever been except maybe jQuery (whose responsibility was a lot narrower). It's not my personal favourite, but I appreciate having a de-facto standard across almost everything I have to work on.

Google is mostly irrelevant to this discussion: they have extreme Not Invented Here syndrome and cling to terrible in-house technologies for front-end development. (They have been gradually increasing their React adoption over the last few years, but it will probably remain small.) They were once the world leader in JavaScript development, but they haven't treated it seriously and are now significantly worse than all of their competitors. If you go to Google, you're committing to learning a lot of non-transferable knowledge (in addition to a lot of valuable practices).


I know it will never happen, but something like LINQ in C#. Not just functions that iterate over collections, but the whole idea of language integrated queries where code is turned into expression trees that can be parsed and translated by providers.

For instance in C# you can take the same LINQ expressions and they can be translated to sql by EF or MongoQuery by the Mongo driver at runtime.


Without type safety, there doesn't seem to be much to gain from this besides for a bit of syntax checking?


In this case of LINQ, I can call methods like:

var retiredMales = custRepo.Find(c => c.Age > 65 and c.Sex == “M”)

and then have an interface with a member:

List<Customer> Find(Expression<Func<Customer,bool>> expression):

You can implement three classes for that interface - one that uses EF, one that uses Mongo, and one that just uses in an memory List for unit testing and they all translate the expression to the underlying data store.

Theoretically, you could have an “expression” type in JS that can be parsed by the provider.


What you’re looking for is the backtick in lisp. Or more generally: lisp macro’s where a function can inspect the ast of an argument and base it’s result on that.

For those who gave never used linq: this allows the above function to translate the function to a sql, sparql or mongo query depending on the currently selected backend


As an aside, I’m not seeing any novel features in this list. All of them seem to be borrowed from other languages (Swift, F#, Haskell, ...).

With enough of this, at what point will all languages start to look identical (Youngme Moon calls this “homogeneous heterogeneity”)?

And how often does a language come along that introduces something truly new, rather than porting an idea from another popular language?


Why should every city have a theatre?

Is there any reason to "leave features to others" if they make sense in your own language? The only issue I can come up with is that some language might die off because it doesn't have any unique features. But if that happens and the momentum is not enough to keep it alive, that doesn't seem like loss to me anyway.


I don’t know, it just feels like a bit of a boring world if every language looks the same, instead of prodding you to look at the world from a slightly different angle.


I really don’t see this criticism. Yes, all of these features have existed in some form in prior languages. But I wouldn’t say that this makes JS “look like” Haskell.


Agreed. But if F#’s pipe operator makes it to Hack and JavaScript, null chaining makes it to C#, CoffeeScript, and JavaScript, value types make it to Haskell, Clojure, and JS, etc.. Eventually the overlap between different languages’ features grows larger.


Like, I absolutely agree with you here. Languages are starting to stabilise when it comes to syntax, features are getting cross pollinated from other languages faster, etc.

I think it's wonderful. Developers can move between languages interchangeably, teaching burden is reduced, languages aren't dismissed because they lack feature X.


That’s a valid way to look at it, too.


The pipe operator is such a simple bit of syntax I see no reason why it shouldn’t be in most languages. No one complains about the addition operator syntax looking the same in most languages.


Slightly different angle... but for me, the special thing about Javascript is that it comes with a whole web browser. Being able to do the stuff those other languages can do in the browser makes me happy :)

I especially like the pipe operator, even though I think the `|>` is kinda clunky asthetically.


Since everyone is coming from different language backgrounds, I wonder if it’s better to treat JS/WASM as a low-level compile target, and simply compile peoples’ language of choice to JS (OCaml via Bucklescript, Scala via ScalaJS, Haskell via PureScript, Clojure via ClojureScript, etc).

That way you avoid bloating the core language with everyone’s pet features (I’d also love pipe syntax, but am not sure it belongs in JS core).


In principle, it's a fine idea, if we started with something like wasm. But JS the language is already horribly bloated in many ways, so it's hard to justify avoiding new features on the basis of KISS - that ship has sailed a long time ago.

(OTOH, note that nobody is proposing to add this sort of high-level stuff to wasm. That's because it's designed to be a compilation target, and it's actually good at it.)


You’ve got to have a solid base before you jump to innovating and introducing something “truly new”.


Here are things that I've added in Sciter's script (https://sciter.com):

'like' operator that is used as a match

    if( obj like /[A-Z]/g ) … // "string".match
    if( obj like {name:String} ) … // obj has name string field
    if( obj like [String,Object] ) … // obj is an array of at least two elements, string and object.
extended switch() :

    switch(obj) {
      case 1: … // standard JS    
      like /[A-Z]/g: … // RegExp, Object, Array match
      instanceof cls: … // obj instanceof cls 
      in [1,2,3]: … // obj  
    }


Object literals to allow `-` in identifiers, so these are valid constructs:

    var style = {
      font-size: 10px, 
      border-width: 1em
    }  
New built-in value types: Length, Duration, Angle, Color. So these are valid constructs:

   this.timer(200ms, function() {...});
   
   element.style.width = 1em;
    
   var c = rgb(128,128,0);
   assert c.r == 128; 
   var (h,s,v) = c.toHSV();
Float and Integer are distinct types as these are different entities indeed.


'in' operator follows `contain` idiom for collections.

    1 in [1,2,3] -> true
    
    "foo" in {foo:1,bar:2}  -> true
Real namespaces:

    namespace Foo 
    {
      function a() { return "a"; }  
      function b() { return a(); } // Foo.a() call
    } 

    namespace Key { // enums
      const ENTER = 13;
      const SPACE = 32;
    } 

    if( evt.keyCode == Key.ENTER ) 
      ...


by value and by reference comparison:

   [1,2,3] == [1,2,3] -> true in Sciter, false in JS
   [1,2,3] === [1,2,3] -> false in Sciter and in JS

   {a:1} == {a:1} -> true in Sciter, false in JS
   {a:1} === {a:1} -> false in Sciter and in JS


1.1 Comparing objects by value I _think_ I'd prefer a standard `equals` method, or more likely a `[Symbol.equals]` method. With `[Symbol.equals]` and `[Symbol.hash]` we could also build collections for objects.


After messing with Elixir for a while, i really enjoyed the piping feature. I’m sure it will be added to js and I’m genuinely excited cause it makes reading code easier for me, given that your function names tell you what the function does


I wish more languages follow the sense of being fun to code like ruby.

[1, 2, 3] - [1, 3] #Yields [2]

The code is readable, intuitive and short yet many languages never have this simplicity.

JS example. https://stackoverflow.com/questions/1187518/how-to-get-the-d...


That doesn't seem super intuitive to me. I assume that

    [1, 2, 3] + [1] == [1, 2, 3, 1]
but then if I subtract [1] again, does it take away the first 1, or the last 1?

It turns out it removes both of them. So subtracting a list is not the inverse of adding a list. In Python, there is the distinction between lists and sets, and while both support the + operator, only sets support the - operator for this reason.


First one is called 'concatenation', second 'array difference'. It does require a one-time look at documentation, which doesn't take anything away from the fun or practical aspects. There is no expectation that they are inverse or commutative.


> There is no expectation that they are inverse or commutative

Please. I am not a Ruby coder but I fully expect any language that a + b - b is just a and also a + b - a is just b.


Uh, floating point numbers don't.


These are not mathematical operators (hence the “overloading”).


The takeaway is that you can't have the single best-defined array difference---it is badly adapted from the well-defined set difference. There are tons of possible values that `[1a, 1b, 1c, 1d, 2, 3] - [1e, 1f, 2]` can yield (where `1a` through `1f` all compare identically but have different object identities):

    [1b, 1c, 1d, 3] (remove the first occurrence only, no matter the RHS has many occurrences)
    [1a, 1b, 1c, 3] (remove the last occurrence only)
    any of [1a, 1b, 1c, 3], [1a, 1b, 1d, 3] etc. (remove any single unspecified occurrence)
    [1c, 1d, 3] (remove the first K occurrences where K is the multiplicity)
    [1a, 1b, 3] (remove the last K occurrences)
    any of [1a, 1b, 3], [1b, 1c, 3], [1c, 1d, 3] etc. (remove any K unspecified occurrences)
    [1a, 1b, 1c, 1d, 3] (remove a whole group of equal occurrences only when multiplicites match)
    [1a, 3] (flatten both the LHS and the RHS and calculate the set difference)
    [1d, 3] (same as above, but the set flattening prefers later occurrences)
    any of [1a, 3], [1b, 3], [1c, 3] or [1d, 3] (same as above, but the set flattening is unspecified)
    any of [1a, 3], ..., [1d, 3], [1e, 3] or [1f, 3] (same as above, but without any guarantee of the identical element)
    runtime error, as there are multiple same elements in the LHS and/or the RHS
    [0, 0, -1, NaN, NaN, NaN] (okay, I'll stop being clever and calculate actual numeric differences)
See? The "array difference" is not enough. Something like the "array difference with multiple occurrences removed in the order of appearance" would work, but this refutes the OP's claim of the "array difference" being "readable" and "intuitive".


I don’t see what’s complex about it: make a copy of the first array excluding any elements that exist in the second. Only one outcome possible.


The multiplicity is important if you see an array as a bag, i.e. a multiset. Some may reasonably expect the pairwise difference, which is indeed a default mode for vectors and matrixes (even C++ has one, `std::valarray`). There are also tons of implementation concerns I haven't ever dabbled.


If you're talking about sets, then yes. Doing that on lists would be awful. Lists don't have the same guarantees as sets, so the operator would have plenty of behavioral ambiguity. You'd have to learn the convention of the operator for edge cases where the operation is not well defined.


What we really need isn't more syntax, it's core features like threads, operator overloading, a large std library, some real form of serialization (JSON doesn't come close to counting), etc.


Or even more basic things like "a date/time implementation that isn't a horror show" or "\w that behaves like \w in every single other regex implementation out there when the unicode flag is on".


How is operation overloading a core feature and not more syntax? I like the infix proposal better. Operation overloading has a larger degree of obfuscation.


IMHO the list of changes presented here sort of half way gets you there to the (apparently) platonic ideal of being more swift or Kotlin like. It's better but seems a bit lacking in ambition because of backwards compatibility.

I'd argue backwards compatibility is actually a lot less important in the JS world than it currently appears to be. Why not aggressively deprecate all the uglyness, broken semantics, etc. If you write ES 2020, or whatever they will call it, why would you still want broken floating point math, wacky equals behavior that nobody can explain with a straight face, etc. Just deprecate it, start disallowing it, and eventually remove it from the language in a couple of versions.

Browser javascript is effectively a compilation target these days. Yes you can write in it directly but many people don't do that. Transpilers seem to be the norm these days. Mostly transpilers already do work around javascript legacy but they still have to deal with hiding some of the ugliness.

The good news is that people update their browsers. So there is very little need for transpilers to be able to target old versions of browsers. Mostly they just need to deal with current and recent versions.

With the emergence of WASM, that is becoming a more obvious compilation target. You can already run C, Kotlin, Rust, Code, etc. using it. Doing so, you bypass all of the legacy of javascript. It's just another compilation target for compilers.

It's not such a big leap to simply run javascript interpreters/vms on top of WASM as well. Why not? It's not any different from doing the same with ruby or python. It would be a great way to support legacy applications and it completely removes the need for javascript to continue to support legacy features. IMHO, it would greatly simplify browser implementations if all they had to do is support running WASM + whatever standardized bindings they ship for things like the DOM, browser storage, webgl, etc.

You could freeze a current version of v8, call it legacy js and use that to run legacy javascript if/when you actually need it and rely on a modern JS interpreter without any of the baggage. My guess is that running legacy js would very rapidly become a niche thing.


A built-in way to conveniently iterate n times.

Anyone have a sensible pure JavaScript example for:

    range(5).forEach(fn)
       
I haven't found one that felt small enough to use inline. It end up using traditional for loops.


Yeah, this

   for (var n in 5) // n is in [0..5)
should just work as it does in Sciter.


Using underscorejs or lodash:

    _.range(5).forEach(i => {/*...*/})


I really don't love it, but

    Array(5).fill().forEach(() => console.log('hello'))


I've also taken to:

    Array.from({ length: 5 }).forEach(...)
But I'm not sure what you gain from that compared to just wrapping a for loop in a function. You still don't get ranges the way you do in, say Ruby:

    ('a'..'z').each {|letter| puts letter }


Yeah, I mostly work in Python, where ranging and slicing is amazing. JS has a lot to catch up on in that regard.


Array.from({ length: 5 }).forEach(fn)


Oh not bad. I wasn't aware of that!

I'm guessing it constructs the whole array upfront. Maybe there's a generator way to do it too.


For value equality, I feel like the whole value/reference type distinction is an unnecessary distraction. What we really need is different operators to handle object identity comparisons and value comparisons. Whether an object can be meaningfully compared by value to another object is orthogonal to whether it's a value type or not - e.g. arrays are reference types, but there's no reason why any two arrays can't be compared element-by-element.

One language that gets it mostly right is Python, where "==" always compares values, and "is" always compares references. Thus, you can use either for e.g. lists and sets, depending on what you actually need at any given time. However, Python fails in that it doesn't give the same flexibility in many contexts where the comparisons are implicit - e.g. in dicts and sets (it always uses ==, instead of letting the user specify an equality function).

Ironically, a language that gets it fully right - including the standard library - is VB.NET.

I'm not even sure that value types themselves are such a useful concept. A value type can be thought of as an immutable reference type, where any mutations done via the reference simply cause a new object to be created and the reference changed (semantically; under the hood, it can be easily optimized down to in-place update).


In this code please remove the when keyword if possible. It's only noise. The Elixir equivalent works well without it. Other than that I'd love that kind of pattern matching on JavaScript.

    case (resource) {
    when {status: 200, headers: {'Content-Length': s}} -> {
     console.log(`size is ${s}`);
    }
    when {status: 404} -> {
     console.log('JSON not found'); 
    }


They're going to have to iterate on that proposal IMO, until they've reached something closer to Rust. C# 8 went with this[1], and that should be proof in the pudding that you can make something more ergonomical/palatable in a C-based language.

[1]: https://blogs.msdn.microsoft.com/dotnet/2019/01/24/do-more-w...


why no one proposes as feature a header to make javascript strict with the newest standard and deprecate and drop compatibility from previous versions in the newer ones?

Or even just complete support for asmjs with interop and program in completely other languages, especially since the person who wrote this seems to want to program in languages with different semantics...



honestly, I would see dropping features and select a leaner ES6+ version within the html tag as a very good "feature". The strict mode also does not fix some historical inconsistencies and design bugs just preserved for compatibility, for example methods on default arrays vs methods on the querySelector results.


The title is "Future JavaScript: what is still missing?"- he's talking about new language features.

I wouldn't expect him to discuss "dropping compatibility" and "completely other languages"


My point is, why continue bloating javascript if its main weakness are some of its historical design issues (preserved only for backwards compatibility.)


Yes, this is a big issue for JS. So much effort is spent trying to make JavaScript be some other language instead, by developers who are stuck building stuff in JavaScript because they want to run it in the browser.

At this point programming in other languages makes more sense and is arguably a better vision for “the future of JavaScript.”


Actually it is a pity that so many languages compile to JS but have an FFI which is not really friendly towards the programmer or that requires some code to write adaptors etc. Let's see how it evolves with asmjs.


What is the goal you want to achieve with this? If just programmer discipline, then that's what many people use linters for.


I do not think discipline is a reliable factor in many aspects... once you try a good compiler or a well designed/consistent API there is no way back. Look for example at C++, no one knows all of it and the newer a developer is the more they want to use all its features, and then there are the templates... which do not add much of clairity or sanity.


That's why people use linters: to enforce that discipline. No need for a special language keyword.


Much of this reads like the author wants JavaScript to be something it isn't. The requests for compare-by-value data structures, immutability, and true concurrency are huge paradigm shifts from the heart of the language whose implementation would no doubt come with significant complexity overhead.


It'd be like writing a post saying "all that C++ still needs are fully-dynamic data structures and garbage collection"

Well, uh, okay


That will be D language then: https://dlang.org/


Exactly- a new language, not a retrofit on an existing one


I'd like to see guard clauses. Don't know how they'd be implemented though.


Not sure if it had been mentioned already, but I think we could benefit from cancellable Promises.

Many of the modules and libraries currently support Promises, and `async/await`, but try to find a way to cancel a long-running Promise. Some libraries have resorted to using their own mechanisms, and some (e.g. `axios`) resorted to using a withdrawn TC39 proposal.

I'd like a standard, easy way to cancel a Promise. I know it's not simple, and Promises can wrap around other Promises, etc. but if I'm e.g. uploading a large file to a server, there should be an easy way to provide my users with a "cancel" button that works.


theres the AbortController [1] that can be used on a few new promise based APIs such as the Fetch API [2]

[1] https://developer.mozilla.org/en-US/docs/Web/API/AbortContro...

[2] https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API


But there's no easy way to inject this AbortControl into existing libraries, or use it easily with async/await. I'm looking for an easier standard.


I love these proposals and while it probably makes sense for me to learn more about the languages that already implement it there's no denying that having them in JS is a win. =)

I wish I knew about python's [itertools](https://docs.python.org/3/library/itertools.html) before I started making [streaming-iterables](https://www.npmjs.com/package/streaming-iterables#api) for example.


Simplicity and elegance


I feel like you could take a subset of JavaScript and make a new "JSLite" language that forces you to do things in one, proper best way. It would be a great way for new developers to learn modern techniques.


Reading is more frequently than writing in reality.


Yes, it looks modern JS and C++ are on the same road now.


Entirely off-topic (or maybe not given the nature of like 20% of the comments here), but it's getting seriously annoying how in every thread even tangentially related to programming there are ten or so people (not the same people, not trying to call anyone out) trying to proselytize about functional programming and/or static typing.

They're not panaceas, they're just alternative approaches to programming no worse nor better and they don't need to be brought up in every. Single. Post.


How can you say they're no worse nor better? That's a loaded statement without qualification. It is entirely possible (if not highly likely) that one approach is better (for all or most use cases)


> That's a loaded statement without qualification.

That "without qualification" is an odd thing to say regarding a couple of (flamewars) discussions that have been going on for sixty years. There are thousands, millions of lines of literature discussing advantages and disadvantages of every paradigm and every type system.

> It is entirely possible (if not highly likely) that one approach is better

And when such approach is discovered and/or conclusively proven as better, I'm sure the entire industry will converge upon it. Computer science is, after all, a science.

What I just wrote is obvious and common knowledge that I'm sure you're aware of, but I couldn't think of another way to explain my point. Hopefully I was clear.


JavaScript still is often extended via inheritance, such as classes and modules. This is unfortunate because inheritance is the opposite of simple, putting things together instead of taking them apart.

One addition that could greatly improve simplicity is assignable blocks. A block provides scope, much like a function, but accepts no input and returns nothing. Most functions I write take no input and return undefined because I only need them for code reuse by reference.

Imagine code like this:

    if (expression) myBlock else otherBlock;


What's the benefit over myBlock() and otherBlock()


You can write code withou the curly braces, but is generally considered a bad practice.

    if (expression) myBlock() else otherBlock()
is really

    if (expression) {myBlock();} else {otherBlock();}
In that case a block is still there and not reused. Both the semantics and structure are different if the block itself were reused. You could extend the above like so:

    if (expression) {myBlock();x+=1;} else {otherBlock();}
If instead the block were reused that would not work. A finer degree of separation is imposed by reference that reenforces separated structures by name. Separation is the foundation of simplicity. It also makes for code that is easier to read like a natural language sentence.


I'm still not following. Why would you want to reuse blocks (scopes?) rather than code?


Types :) I tend to use underscore for just _.get or _.set these days as I have most everything else I want so having some built in way to do that would be nice but the elephant in the room here is the toolchains being built quietly behind the scenes that support web assembly from strongly typed languages with much better load times and performance. The WALT one and the oCaml React one is pretty cool but would be nice if there was an official strong-typed JavaScript version for this scenario being built as a spec.


Ironically though, using _.get or _.set makes that code virtually impossible to type.


right because i wasnt sure of the type


Renderer access. It should be possible for js to control and get callbacks for rendering and event updates in styling. Though that's a browser feature, not really js


You might be interested in the CSS Paint and Layout Worklet proposals.


I'd like to see a real, useful implementation of weak references, for use in value caches, or a way to manually take over garbage collection for some objects.

Basically if I want to keep a cache of expensive objects that will grow monotonically, I need to do a lot of work to make sure I'm not leaking memory right now, but if I could instead store in a Map<string, weakref>, it'd be much easier, things could go out of ref and I could deal with that.


You're aware of WeakMap and WeakSet, right? You can't key by strings, but I'm not sure you'd want to.


Yeah weakmap and weakset basically do the opposite of what I want. The keys are weakly held rather than the values, which does no good for an ORM cache. One cannot even iterate over the keyset in a weakmap (so you can't do a lousy o(n) lookup version of what I want.


Yes, but a weakref by string or number would be quite useful for ORM caching.


The single most important feature I'd like to see in JS are types. Typescript seems nice but I don't want to need a transpiler, especially since I'm doing a lot of live-coding in JS and compilation/transpilation times >50ms are already too much.

Other than that, JS could benefit tremendously from a more complete standard library, and a build system, including a file watcher, that doesn't rely on 200 nonsensical dependencies.


I'm confused -- any static typing system is going to need a compilation step to check types. How is typescript any different than "javascript with types" would be? If your goal is live coding, a dynamically typed language will always be faster.


Typescript right now requires basically two compilation steps, and it doesn't have decades of compilation optimizations behind it that JS has. If types or typescript itself, were directly supported, things would be so much faster.


Reminds me of this quote:

“Perfection is achieved when there is nothing left to take away.” --Antoine de Saint-Exupéry

This principal should be applied more to programming languages.


Type safety


Typescript!


Typescript is missing from JavaScript?


If Apple is indeed granted the patent on Swift's optional chaining (as was recently posted https://forums.swift.org/t/apple-is-indeed-patenting-swift-f... ), we wouldn't see this in future Javascript, would we?


Groovy had the same feature well over a decade ago[1] (at least in 2005) and other languages possibly even before that. Swift was first announced in 2014.

Swift's is also tied to their `Optional<T>` wrapper type, any potential JS implementation would be more like Groovy's.

[1] https://web.archive.org/web/20050815012153/http://groovy.cod... , bottom of the page


I'm no lawyer, but I think the Apache2 license on Swift would mean that the JS community has access to the patent.

https://github.com/apple/swift/blob/master/LICENSE.txt

TC39 (who defines Javascript) is a collaboration of several companies, so I'm not sure who Apple could/would even sue if this was proposed and adopted for JS. I doubt we would see legal activity over it.


That sounds like a truly ridiculous patent! Optional chaining is just another syntax for the Maybe-monad, surely?

Which has been around since 1998 at least (When Haskell 1.3 added monads)


I would have thought that Coffeescript's optional chaining, present at least as early as v1.0 in 2010[0], would have counted as prior art.

[0]: https://github.com/jashkenas/coffeescript/blob/1.0.0/lib/par...


Is it even a useful feature?

Will it lead to many JS bugs simply disappearing and applications getting more stable or will it just obfuscate bugs in the long run?


Presumably people will only use it in situations where null (or undefined, or however it works) is a valid and expected value for a particular property.

In TypeScript, these properties would explicitly have a `MyType|null` or `MyType|undefined` type.


Roughly speaking, one less place where we need to use lodash.

"or will it just obfuscate bugs in the long run?"

Probably. :shrug emojii:

But more stable in the sense of turning hard crashes into UI quirks.


I wish JavaScript would let me declare scoped variables inside if statements like this:

    if (let myVar = exp) {


Should this also behave like if-let in rust so if the pattern doesn’t match then it acts as false? E.g

  if(let [x,y] = thing) ...
Would only be true when thing is an array with two elements (not super sure bow pattern matching is defined in ha. I would guess that actually if the match fails then things get to be undefined which means the match can’t really fail so this would likely be confusing)


The biggest thing that is missing in my experience is finalizers or context blocks. I need some way to free resources allocated inside emscripten. Either python style context managers or go style defer or c++ style destructors on scopes would be a god send.


As someome who has spent the last year primarily with Elixir, it is impossible to overstate how nice pipeline |> is.

It is one of the most convenient and readable pieces of syntax, and one of the primary reasons I pause before using javascript instead of Elixir in a given moment.


Some things I wanted in JavaScript is a goto command, and macros. They mention objects to compare by value, and I had a bit different idea which is a UserPrimitive function which has a similar meaning (but different; user primitives would also allow some degree (but still restricted in some ways) of operator overloading).

Bigint is a good thing they added. Sometimes you need to deal with 64-bit integers, and other times you might want unlimited range, so that helps. However, some operations that are not currently built-in perhaps should be in order to be more efficient, such as log2 of bigint numbers or counting trailing bits.

A built-in RegExp.quote() function is another thing (although it is easy to make, it seem to me it is something that ought to be built-in).

If macros are implemented, then many features can be done by macros and do not need to be built-in features.

One feature of JavaScript that I dislike is automatic semicolons.

And yet, many proposals (including these) may be rejected if better ways are found.


The only thing I absolutely hate is the import-as bullshit inspired by Python. I was told this was to assist static analyzers, but when you compare it to destructuring syntax you just get annoyed by the duplication.

+1 for pipeline operator +1 for a destructuring switch


I like JS, I like the new features since es5, although I liked es5 also, I notice that the updates are now yearly it seems, which is mostly good since there was maybe a long delay in the past, but can be bad if there become too many features.


Coroutines (unlike async/await) would allow one to wait on an async result without losing the stack which is waiting. This would avoid the need for (often) inscrutable callback state machines (Javascript's version of GOTOS).


Async/await has a logical stack that is distinct from the physical stack(s). Quality tooling would show you said logical stack instead of a mish-mash of physical stacks from different async functions that happen to be intermingled on the current thread. Example:

https://blogs.msdn.microsoft.com/devops/2013/07/01/debugging...

So you don't need to deal with "inscrutable callback state machines" anymore so than you need to deal with the assembly labels and jumps that your for-loop compiles into.


Not having to install the shit show of conflicting dependencies. Get a formal package manager instead of the twenty different ones. Basically take a look at python and realize the js ecosystem could actually have nice things.


Restraint.


Save navigation and equals/hashCode (as protocol, not using that ValueType thingy) are fine and definitely useful, but otherwise we should avoid adding even more syntax elements to JS.


I would love some of the syntactic sugar from Swift. Swift's `guard` and `if let`, along with the nil coalescing mentioned in another comment make Swift a joy to write.


I know this is a really contentious idea but I'd like javascript and typescript to merge into the same language. Type safety is the huge 800lb gorrilla in the room.


I know at least I will never ever go back to JavaScript after having used TypeScript for a bunch of years.


> Comparing objects by value

A handy helper I use all the time for this:

  export const jsonEqual = (a, b) =>
  JSON.stringify(inspect(a)) === JSON.stringify(inspect(b))


I would like optional typing for the few places where it would be very useful. But people would enforce all types way too much, so I'm kind of on the fence.


Please stop adding new syntax, it only makes the language uglier. Instead of a special syntax for comparing objects, just add a function to Object.prototype.


I disagree. Stuff should not be added to Object.prototype, although it can be a function in Object itself (such as Object.equals or something, not Object.prototype.equals). You are correct it does not need a new syntax though.


I'm implementing delta JSON mutations. I think with time people will embrace the idea.


Python writes to local files using native function open. Javascript has no native function. Because...?

We can open a window, write arbitrary text to it, use the browswer to 'Save as...'. Close but no cigar.

I'd like to use JS for a lot more than the web. MS Basic wrote user-entered text to a file 40 years ago.


I’m not sure about you, but I consider JavaScript running in my browser not being able to write arbitrary text to files to be a good thing.

Otherwise, Node.js can do exactly what you just said Python and MS Basic can do.


Whoa hoss. Bit of overkill there.

I can already write to file with 'window.(something).createObjectURL' ... which queries user. I simply suggest that 'future javascript' open() (or whatever) supports that with no-more-code needed.


Really? Still no way to specify argument types?


Just argument types are useless.

Either all variables shall have types and so static, compile time analysis can be performed.

Or no types at all.


Useless to you maybe.


To anyone.

Types are useful only if they can be checked/verified at compile time.

Types in only arguments cannot be verified by compiler. So type mismatch can be checked only at runtime. So would be this:

   function foo(a:Number) { … }
Is essentially this:

   function foo(a) {
     if( !(a instanceof Number)) throw "Not a number";
     … 
   }
And that rises the question: who and at what moment will handle such errors?


Types are a subset of contracts. Contracts are plenty useful at runtime, as evidenced by the fact that every language that offers design-by-contract (most of which are statically typed) still provides runtime checking facilities. Furthermore, they're usually enabled by default for preconditions, which is what argument types are.

Nobody will handle such errors - contract violations are not supposed to be handled, they're a coding bug. You "handle" them by fixing the caller such that it never violates the contract. The benefit of having a runtime check is that it can detect the error instead of silent wrong behavior potentially followed by state corruption and further bugs. Most of the time, these will fire in automated tests, anyway.


They’re plenty useful if you want to write a library and distribute it (either publicly or within an org) that tells users of the library what they did wrong (other developers) without having to insert type checking boilerplate into every single method.

More

Applications are open for YC Summer 2019

Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact

Search: