Modules: Curly Free
Thanks, I do agree with most of your points, but it doesn't take into account anonymous export. I'm gonna bite the bullet here and tempt the bikeshedding demons by making an incremental suggestion for a syntax for anonymous import/export that adds to your syntax.
export default f(1, 2, 3); // creates anonymous export by evaluating RHS expression
import default as foo from "foo"; // binds the anonymous export from module "foo" to variable foo
The downside of this is that it's a little chatty for what we expect to be the common case of modules with just a single export or modules where the vasty majority of the time you want the default export. But it has a nice symmetry and builds on your (valid) points.
On 16 April 2013 18:55, David Herman <dherman at mozilla.com> wrote:
I'm gonna bite the bullet here and tempt the bikeshedding demons by making an incremental suggestion for a syntax for anonymous import/export that adds to your syntax.
export default f(1, 2, 3); // creates anonymous export by evaluating RHS expression import default as foo from "foo"; // binds the anonymous export from module "foo" to variable foo
OK, you asked for it. How exactly is that superior to
export let it = f(1, 2, 3)
import it as foo from "foo"
which is both shorter and does not need any extension to the syntax at all?
On Apr 16, 2013, at 10:20 AM, Andreas Rossberg wrote:
On 16 April 2013 18:55, David Herman <dherman at mozilla.com> wrote:
I'm gonna bite the bullet here and tempt the bikeshedding demons by making an incremental suggestion for a syntax for anonymous import/export that adds to your syntax.
export default f(1, 2, 3); // creates anonymous export by evaluating RHS expression
import default as foo from "foo"; // binds the anonymous export from module "foo" to variable foo
OK, you asked for it. How exactly is that superior to
export let it = f(1, 2, 3) import it as foo from "foo"
which is both shorter and does not need any extension to the syntax at all?
or
export let foo = f(1, 2, 3);
import foo from "foo";
OK, you asked for it. How exactly is that superior to
export let it = f(1, 2, 3)
import it as foo from "foo"
which is both shorter and does not need any extension to the syntax at all?
It is seen as a deficiency (anti-idiomatic?) by some members of the development community to have to rename the "one thing" at all. I remain on the fence about it. Maybe developer outreach would be more effective than syntax, in this case?
On Apr 16, 2013, at 10:54 AM, Kevin Smith wrote:
OK, you asked for it. How exactly is that superior to
export let it = f(1, 2, 3) import it as foo from "foo"
which is both shorter and does not need any extension to the syntax at all?
It is seen as a deficiency (anti-idiomatic?) by some members of the development community to have to rename the "one thing" at all. I remain on the fence about it. Maybe developer outreach would be more effective than syntax, in this case?
But in this case the "one thing" doesn't actually have a pre-existing name so nothing is being renamed.
Also, if the export keyword is currently always followed by a declaration keyword (let
, const
, class
, function
) there would seem to be a good chance that syntactically no second keyword could be interpreted as an implicit let
(or const
), so:
export foo = f(1, 2, 3); //means same thing as: export let foo = f(1,2,3)
I haven't actually analyzed the grammar implications, but it seems plausible.
On Apr 16, 2013, at 10:20 AM, Andreas Rossberg <rossberg at google.com> wrote:
On 16 April 2013 18:55, David Herman <dherman at mozilla.com> wrote:
I'm gonna bite the bullet here and tempt the bikeshedding demons by making an incremental suggestion for a syntax for anonymous import/export that adds to your syntax.
export default f(1, 2, 3); // creates anonymous export by evaluating RHS expression
import default as foo from "foo"; // binds the anonymous export from module "foo" to variable foo
OK, you asked for it.
Indeed... :-}
How exactly is that superior to
export let it = f(1, 2, 3)
import it as foo from "foo"
which is both shorter and does not need any extension to the syntax at all?
Because character count is not the only measure of clarity. And requiring a naming convention imposes standardization costs. We're the standards body! Not only that, but there's also the problem of interoperability with existing code (AMD, NPM, etc) that uses the "single dynamic export" idiom.
Not only that, but there's also the problem of interoperability with existing code (AMD, NPM, etc) that uses the "single dynamic export" idiom.
There is that. We've floated some different ideas in the past for dealing with this but Andreas dislikes them all. :) Here's another option that we should at least throw on the table:
Not support interoperability. Load legacy modules with legacy methods and ES modules with ES syntax.
NPM modules which are upgraded to ES modules can still maintain backward
compatibility with require
by continuing to set module.exports = expr
.
An upside to this solution would be that actively maintained NPM modules would be pressured by users to migrate up to ES modules, thereby moving the codebase at large away from legacy module systems.
A downside to this solution would be the "pitchfork" risk.
On 16 April 2013 20:36, David Herman <dherman at mozilla.com> wrote:
On Apr 16, 2013, at 10:20 AM, Andreas Rossberg <rossberg at google.com> wrote:
On 16 April 2013 18:55, David Herman <dherman at mozilla.com> wrote:
I'm gonna bite the bullet here and tempt the bikeshedding demons by making an incremental suggestion for a syntax for anonymous import/export that adds to your syntax.
export default f(1, 2, 3); // creates anonymous export by evaluating RHS expression import default as foo from "foo"; // binds the anonymous export from module "foo" to variable foo
OK, you asked for it.
Indeed... :-}
How exactly is that superior to
export let it = f(1, 2, 3) import it as foo from "foo"
which is both shorter and does not need any extension to the syntax at all?
Because character count is not the only measure of clarity. And requiring a naming convention imposes standardization costs. We're the standards body!
I don't understand. Are you saying that it has a higher cost to standardize a trivial convention than it is to standardize additional ad-hoc syntax?
Not only that, but there's also the problem of interoperability with existing code (AMD, NPM, etc) that uses the "single dynamic export" idiom.
Sure, but that's probably equi-distant from both solutions, i.e., neither makes that easier or harder than the other. At least I don't see how.
/Andreas
Andreas Rossberg wrote:
I don't understand. Are you saying that it has a higher cost to standardize a trivial convention than it is to standardize additional ad-hoc syntax?
Answering for Dave: you bet it is.
The cost of the former is born by everyone in a large-N community who must learn the "trivial convention". The cost of the latter is born by we few TC39ers and JS implementors, who can make that sacrifice.
Recall Mr. Spock's Kobayashi Maru solution from STII:TWoK.
The cost of the former is born by everyone in a large-N community who must learn the "trivial convention". The cost of the latter is born by we few TC39ers and JS implementors, who can make that sacrifice.
Yeah, but it's a false dilemma, I think. No trivial naming convention is necessary, and no ad-hoc syntax is necessary. Asking the developer to name a thing with a well-chosen identifier is completely reasonable in my book.
Further: with the right syntax in place exporting more than one thing will likely cease to be an anti-pattern.
Also, a proposal-gist of curly-free syntax and inline modules:
On Apr 20, 2013, at 10:36 AM, Kevin Smith <zenparsing at gmail.com> wrote:
Yeah, but it's a false dilemma, I think. No trivial naming convention is necessary, and no ad-hoc syntax is necessary. Asking the developer to name a thing with a well-chosen identifier is completely reasonable in my book.
I don't really know how to answer opinions like this. It just seems like... it's fine that you feel that way, but kind of irrelevant. Significant communities of JS developers have already spoken -- in words and in precedent -- that they disagree with you. So for both smoother interoperability and continuing to support what is a highly valued use case, it's a tiny price to pay.
Further: with the right syntax in place exporting more than one thing will likely cease to be an anti-pattern.
This misunderstands the value of anonymous export. There's a reason I call it "anonymous" and not "single." This is about having a "main" export, a common case. Even when there's a very low cost to having multiple exports, it's still extremely common for a library to have a particular operation you want to use 90% of the time. So whether you have 1 export or 100, there's still value in anonymous export.
On Apr 20, 2013, at 5:17 AM, Brendan Eich <brendan at mozilla.com> wrote:
Andreas Rossberg wrote:
I don't understand. Are you saying that it has a higher cost to standardize a trivial convention than it is to standardize additional ad-hoc syntax?
Answering for Dave: you bet it is.
The cost of the former is born by everyone in a large-N community who must learn the "trivial convention". The cost of the latter is born by we few TC39ers and JS implementors, who can make that sacrifice.
Recall Mr. Spock's Kobayashi Maru solution from STII:TWoK.
Thanks, and let me also add the following points:
"Standardize additional ad-hoc syntax" is seriously hyperbolic. Anonymous export is simply about allowing library authors to indicate a module's main entry point. Semantically, we're talking about the difference between a string and a symbol; syntactically, we're talking about one production. It's all cleanly layered on top of the rest of the system. Let's keep some perspective.
Moreover, "ad-hoc" seems to suggest some sort of arbitrariness, as if it's not well-motivated. It is in fact well-motivated, by a real requirement that otherwise requires a manual design pattern -- on the part of both the creators and the clients of libraries, note well! In fact, I've seen the design pattern arise in practice in other languages. And yet in existing JS module systems, no such pattern is required of a library's clients. (The clients are of course the important case. Yet another instance of the applicability of Mr. Spock's solution!)
I am constantly, repeatedly met with impassioned requests [*] for anonymous export support by JS developers of multiple communities, e.g., NPM and AMD users alike. Do we wish to just ignore them? Do we favor minor convenience of engine implementors over the readability and clarity of every client of large numbers of idiomatic JavaScript modules?
Dave
[*] G-rated version of the story.
I don't really know how to answer opinions like this. It just seems like... it's fine that you feel that way, but kind of irrelevant. Significant communities of JS developers have already spoken -- in words and in precedent -- that they disagree with you. So for both smoother interoperability and continuing to support what is a highly valued use case, it's a tiny price to pay.
Well, first, I've said before that I'm on the fence on "anonymous" exports. There are more important things at play, and I don't want to get stuck in any land war over it. But to argue the other side for a moment:
-
Supporting anonymous exports makes the model a little more complicated. We can quantify that with number of productions, or lines of pseudo-code, or whatever you like, but that's a fact. All else being equal, simple is better.
-
I don't believe you've really made the case for how anonymous exports will make things better. It's not enough to say that "developers really want it". There has to be a convincing technical argument. I'm not ruling it out, I just haven't seen it yet.
Look, I was there on CommonJS arguing for "exports overwriting". I identified that use case years ago. It didn't just spring forth as wisdom from some JS collective mind. It was entirely motivated by the "remote function call" model of modules in CommonJS (and it's fork, AMD). Basically, this is an nasty, ugly pain to write:
var MyAbstraction = require("./MyAbstraction").MyAbstraction;
Not because you have to name MyAbstraction, but because you have to intone it three times just to pull out the reference. Let's compare that to what you might write with ES modules:
import MyAbstraction from "MyAbstraction.js";
Beautiful! The use case evaporates, does it not? OK, but what about renaming?
import MyAbstraction as Whatever from "MyAbstraction.js";
Still beautiful. You're going to have a hard time arguing that renaming is such an awful user experience here.
And on the provider side, I stand by my claim that requiring an author to actually name an abstract concept is entirely reasonable.
Next, what about interoperability with legacy modules? Well, we need to see an end-to-end demonstration of how that's actually going to work in practice before we admit it as a technical argument in favor of anonymous exports. I've tried to demonstrate in the past that I don't think it's going to work out, but I'd be happy to be proven wrong.
Dave, I know that you've gotten more than your share of flame for the past two or three years on JS modules. But anyone that directs flame your way needs to put up a technical argument, here on es-discuss, or shut up. You can tell them @zenparsing said so. : )
Kevin Smith wrote:
- Supporting anonymous exports makes the model a little more complicated. We can quantify that with number of productions, or lines of pseudo-code, or whatever you like, but that's a fact. All else being equal, simple is better.
You're counting the wrong beans. Languages have affordances which add piecewise complexity to grammars and implementations precisely so that their users can have simpler code.
- I don't believe you've really made the case for how anonymous exports will make things better. It's not enough to say that "developers really want it". There has to be a convincing technical argument. I'm not ruling it out, I just haven't seen it yet.
No, the argument is that users shouldn't have to name the anonymous export and import by that name. Those complexity beans multiply and refract through the large-N community of language users.
I hope not to make these two simple points in conjunction two days in a row!
No, the argument is that users shouldn't have to name the anonymous export and import by that name.
I understand, I'm just not convinced. I'm still on the fence.
Anonymous export is simply about allowing library authors to indicate a module's main entry point. Semantically, we're talking about the difference between a string and a symbol; syntactically, we're talking about one production. It's all cleanly layered on top of the rest of the system. Let's keep some perspective.
If you put it like this ("entry point"), it recalls another issue, namely that of scripts-vs-modules (executable code vs declaration container).
Would it be possible to combine the two issues, with a common solution?
Something like: modules are importable and callable, importing a module gives access to its (named) declarations but doesn't run any (non-declaration) code, calling a module gives access to a single anonymous export (the return value) while also running any non-declaration code in the module.
Claus
No.
Sorry, let me say a bit more.
You identified the entry-point idea. Good so far.
But then you went too far and made that entry-point, which with anonymous export is often (but not always) a function, with the body of the module, its top-level code.
A module is not a function. It is not generatjve when nested. The current proposal doesn't support nesting, but earlier versions did, and that was critical to the second-class nature of modules when declared.
Mark Miller tried to get generative (callable) modules working here: strawman:simple_module_functions. But Mark just wrote last week: "However, I don't actually have a coherent end-to-end proposal for making this work(strawman:simple_module_functions
doesn't work), and it is clear that I could not agreement on one at the present time even if I had one."
Anonymous export is distinct from callable modules. Some uses of anonymous export make the entry-point an object other than a function.
Brendan Eich wrote:
But then you went too far and made that entry-point, which with anonymous export is often (but not always) a function, with
s/with/be/
But then you went too far and made that entry-point, which with anonymous export is often (but not always) a function, with the body of the module, its top-level code.
I suggested that modules be callable, executing the module body and returning what would be the anonymous export. I did not suggest that the exports themselves need to be callable.
A module is not a function. It is not generatjve when nested. The current proposal doesn't support nesting, but earlier versions did, and that was critical to the second-class nature of modules when declared.
Yes, one would want caching of execution&returns, to keep modules singletons (and to keep anonymous export consistent across imports).
The real fly in the ointment is that JS does not separate side-effects from pure code, so it isn't possible to separate declarations and code execution entirely (the module code needs to be executed, once, somewhere between first import and first use). This currently happens implicitly, and is part of the problem that brought down the earlier lexical modules.
Still, making modules (singleton-)callable would provide a simple syntac for accessing anonymous exports, without interfering with the existing import/export features. Existing AMD and node (single-export) modules could be translated to this, where a full translation to statically checked, named export/import is not wanted.
Claus
I understand, I'm just not convinced. I'm still on the fence.
In any case, the cure must leave us better off than the disease, so let's focus on the other side of the equation and see what kind of cure we can come up with.
Let's say that each module has some "default" export. Let's follow from the Node/AMD example and allow the default "default" to be the module export map. As with Node/AMD, this default can be overridden with some explicit declaration.
On the consumer side, we could do something like this:
// Imports the "default" export, either the module instance object
// or some explicitly specified binding
import "abc" as abc;
That looks nice, but it leaves us without a way to get to the module instance object in cases where the default has been overridden. We could use some lookahead to fix that:
// Imports the module instance object, regardless of explicit default
import module "abc" as abc;
Or we could just leave off the import keyword:
// Another option:
module "abc" as abc;
This also looks nice:
// Another option:
module abc from "abc";
Now to the provider side of things. Dave's idea of using the "default" keyword sounds good to me. We could go fully anonymous, as Dave suggests. If so, then I think we need a "=" to signal to the human reader that an expression context follows:
export default = function() { ... }; // (A)
Or we could flag a regular old export declaration as having the special "default" property, like so:
export default function Z() { ... } // (B)
An upside to (B) is that we can use the exported identifier in an import list:
// With option (B)
import Z, a, b, c from "z-stuff.js";
// With option (A)
import "z-stuff.js" as Z;
import a, b, c from "z-stuff.js";
I think it can be done elegantly. I'm not sure how necessary it is, but I don't think adding a "default" export needs to inflict too much violence on the model or the grammar.
On 21 April 2013 04:15, David Herman <dherman at mozilla.com> wrote:
On Apr 20, 2013, at 5:17 AM, Brendan Eich <brendan at mozilla.com> wrote:
Andreas Rossberg wrote:
I don't understand. Are you saying that it has a higher cost to standardize a trivial convention than it is to standardize additional ad-hoc syntax?
Answering for Dave: you bet it is.
The cost of the former is born by everyone in a large-N community who must learn the "trivial convention". The cost of the latter is born by we few TC39ers and JS implementors, who can make that sacrifice.
Recall Mr. Spock's Kobayashi Maru solution from STII:TWoK.
Thanks, and let me also add the following points:
"Standardize additional ad-hoc syntax" is seriously hyperbolic. Anonymous export is simply about allowing library authors to indicate a module's main entry point. Semantically, we're talking about the difference between a string and a symbol; syntactically, we're talking about one production. It's all cleanly layered on top of the rest of the system. Let's keep some perspective.
Moreover, "ad-hoc" seems to suggest some sort of arbitrariness, as if it's not well-motivated. It is in fact well-motivated, by a real requirement that otherwise requires a manual design pattern -- on the part of both the creators and the clients of libraries, note well! In fact, I've seen the design pattern arise in practice in other languages. And yet in existing JS module systems, no such pattern is required of a library's clients. (The clients are of course the important case. Yet another instance of the applicability of Mr. Spock's solution!)
I'm sorry, but I'm afraid this is one of these moments where I have no idea what the heck you guys are talking about. ;) It is a trivial naming convention, for a single identifier! How is a single name possibly more difficult to learn or remember than any additional piece of syntax, however simple that may be? And how is calling it a "manual design pattern" less hyperbolic?
I am constantly, repeatedly met with impassioned requests [*] for anonymous export support by JS developers of multiple communities, e.g., NPM and AMD users alike. Do we wish to just ignore them? Do we favor minor convenience of engine implementors over the readability and clarity of every client of large numbers of idiomatic JavaScript modules?
I am aware that there are a lot of requests from some communities. If they think this is an important feature to have -- in the context of ES6 modules! -- then we should invite them here to make their case. So far, they haven't, and despite what you say above, I have yet to hear a compelling argument. As Kevin rightly points out, it is a fallacy to extrapolate the need for the convenience of this feature from AMD & friends. As for "clarity" or "readability", I don't see it either.
On 22 April 2013 09:18, Andreas Rossberg <rossberg at google.com> wrote:
On 21 April 2013 04:15, David Herman <dherman at mozilla.com> wrote:
On Apr 20, 2013, at 5:17 AM, Brendan Eich <brendan at mozilla.com> wrote:
Moreover, "ad-hoc" seems to suggest some sort of arbitrariness, as if it's not well-motivated. It is in fact well-motivated, by a real requirement that otherwise requires a manual design pattern -- on the part of both the creators and the clients of libraries, note well! In fact, I've seen the design pattern arise in practice in other languages. And yet in existing JS module systems, no such pattern is required of a library's clients. (The clients are of course the important case. Yet another instance of the applicability of Mr. Spock's solution!)
I'm sorry, but I'm afraid this is one of these moments where I have no idea what the heck you guys are talking about. ;) It is a trivial naming convention, for a single identifier! How is a single name possibly more difficult to learn or remember than any additional piece of syntax, however simple that may be? And how is calling it a "manual design pattern" less hyperbolic?
The fact that explicitly naming things in your modules requires the user to know the internal structure of the module they're dealing with when that isn't exactly necessary adds some complexity (or a "manual design pattern"). Patterns are nasty, patterns are eugh.
At any rate, take these two use cases:
First, someone publishes a module that is intended to use as a middleware for processing incoming HTTP requests. Middlewares are just advice functions that wrap around the main handler to do transformations to the request object — they take one function, return a new function. These things are composed using plain old function composition:
Say, in Node I have a library that works that way, and I want to process JSON requests and output, which are two different modules. I would use something like this:
var json_request = require('json-request-middleware') var json_output = require('json-output-middleware')
compose(json_output, json_request)(http_handler)(80)
Now, if the modules are required to explicitly name their exports, I'd have to remember what kind of name the author of that module used when I'm really only interested in the module itself (or the middleware it is trying to share with me):
import jsonRequestMiddleware from jsonRequestMiddleware import jsonOutputMiddleware from jsonOutputMiddleware
compose(jsonOutputMiddleware, jsonRequestMiddleware)(http_handler)(80)
So far, so good. Nothing that a good memory (which I don't have, btw) can't deal with. Just a little more of cognitive load can't hurt. Problems arise when you start having a bunch of these modules (and with the Node philosophy that's 99% of the cases) and somehow those authors have chosen similar names for their exports. Now you need to remember the name they've used and bind it to another name to solve a conflict, when you could just be doing the binding anyways.
The second use case would be encoding parametric modules by a single function exports. In this case, you need to unwrap the module anyways before you can use it, because it depends on external stuff that you as the client have to provide. Again, this maps rather straight-forwardly to first-class modules in CommonJS.
var dom = require('dom')(cssSelector, eventBridge) dom.query('.foo').on('click', function(ev) { /* event is sanitised here by the eventBridge library */ })
This doesn't map that well to current ES modules because they're second class (unless I've been so out of the loop that I've missed they being promoted):
import dom as domFactory var dom = domFactory(cssSelector, eventBridge)
Requiring people to name that explicitly creates again unnecessary "hops" that might be error prone, and deviate from the purposes of the module. IMHO, that does mean things lose a certain clarity, but on the other hand it could be argued that it just forces people to write code using different design choices. This could mean that you fork the community because you can't provide what one expects from a particular module system, and you end up with a messier state than the current CommonJS/AMD/whateverElse.
Quildreen "Sorella" Motta (killdream.github.com) — JavaScript Alchemist / Minimalist Designer —
On 22 April 2013 15:27, Quildreen Motta <quildreen at gmail.com> wrote:
On 22 April 2013 09:18, Andreas Rossberg <rossberg at google.com> wrote:
On 21 April 2013 04:15, David Herman <dherman at mozilla.com> wrote:
On Apr 20, 2013, at 5:17 AM, Brendan Eich <brendan at mozilla.com> wrote:
Moreover, "ad-hoc" seems to suggest some sort of arbitrariness, as if it's not well-motivated. It is in fact well-motivated, by a real requirement that otherwise requires a manual design pattern -- on the part of both the creators and the clients of libraries, note well! In fact, I've seen the design pattern arise in practice in other languages. And yet in existing JS module systems, no such pattern is required of a library's clients. (The clients are of course the important case. Yet another instance of the applicability of Mr. Spock's solution!)
I'm sorry, but I'm afraid this is one of these moments where I have no idea what the heck you guys are talking about. ;) It is a trivial naming convention, for a single identifier! How is a single name possibly more difficult to learn or remember than any additional piece of syntax, however simple that may be? And how is calling it a "manual design pattern" less hyperbolic?
The fact that explicitly naming things in your modules requires the user to know the internal structure of the module they're dealing with when that isn't exactly necessary adds some complexity (or a "manual design pattern"). Patterns are nasty, patterns are eugh.
Just to be clear, what I suggested was having a single global convention for the name of the 'anonymous' export in every module, say ,"it". No need to know the "internals" of anything. Your Node example,
var json_request = require('json-request-middleware') var json_output = require('json-output-middleware')
would e.g. become
import it as json_request from 'json-request-middleware' import it as json_output from 'json-output-middleware'
(and similarly, on the export side).
The second use case would be encoding parametric modules by a single function exports. In this case, you need to unwrap the module anyways before you can use it, because it depends on external stuff that you as the client have to provide. Again, this maps rather straight-forwardly to first-class modules in CommonJS.
var dom = require('dom')(cssSelector, eventBridge) dom.query('.foo').on('click', function(ev) { /* event is sanitised here by the eventBridge library */ })
This doesn't map that well to current ES modules because they're second class (unless I've been so out of the loop that I've missed they being promoted):
import dom as domFactory var dom = domFactory(cssSelector, eventBridge)
In the syntax Dave proposed that would actually be:
import default domFactory from dom var dom = domFactory(cssSelector, eventBridge)
Is that so notably better than
import it as domFactory from dom var dom = domFactory(cssSelector, eventBridge)
to be worth adding the new syntax? (And semantics, I presume, because Dave hasn't actually told us how the "anonymous" export would be distinguished internally.)
Andreas Rossberg wrote:
Just to be clear, what I suggested was having a single global convention for the name of the 'anonymous' export in every module, say ,"it". No need to know the "internals" of anything. Your Node example,
You are imposing a tax on everyone using modules and anonymous export, today a large base of users (modules are predominately anonymous export by counting NPM modules).
You may be used to "it" conventions in ML, but we're talking about JS here. It's not ML and the communities using modules have already voted, and they will vote with their feet (run away from ES6) if it comes to it.
Finally, I don't think the burden weighting is right in your messages, but I'm still not clear. We could argue forever about whether, in your opinion, there's a use-case that's important enough for you to sink some spec and implementation complexity.
But at this point I believe the use-case is clear: no "it" convention should be needed, because it has not been needed with NPM and AMD. So we're back to arguing about whether to "take one for the team."
On 22 April 2013 15:56, Brendan Eich <brendan at mozilla.com> wrote:
Andreas Rossberg wrote:
Just to be clear, what I suggested was having a single global convention for the name of the 'anonymous' export in every module, say ,"it". No need to know the "internals" of anything. Your Node example,
You are imposing a tax on everyone using modules and anonymous export, today a large base of users (modules are predominately anonymous export by counting NPM modules).
Learning extra syntax is a tax, too. As far as Dave's proposal is concerned, it seems higher to me, without any added value.
You may be used to "it" conventions in ML, but we're talking about JS here. It's not ML and the communities using modules have already voted, and they will vote with their feet (run away from ES6) if it comes to it.
There are substantial differences between legacy modules and what will be in ES6. People will have to adapt, whatever we do. Drawing the "people running away screaming" card is unhelpful rhetoric as long as no valid technical argument is involved. The logical conclusion of your argument would be to just adopt the union of the existing systems, so that nobody has to change their habits. There is good reason why TC39 didn't choose that route.
The more convincing argument would be interop with those legacy modules. But the magic for that will be in custom loaders, and AFAICS, the problems (and solutions) will likely be the same in either approach.
Andreas Rossberg wrote:
Just to be clear, what I suggested was having a single global convention for the name of the 'anonymous' export in every module, say ,"it". No need to know the "internals" of anything. Your Node example,
You are imposing a tax on everyone using modules and anonymous export, today a large base of users (modules are predominately anonymous export by counting NPM modules).
Is there a way to be explicit about what the tax actually is? Andreas’ convention looks like this:
export let it = someValue;
import it as foo from "foo";
If you replace "it" with "default", you almost get David Herman’s proposal, which looks like this:
export default someValue;
import default foo from "foo";
The advantage of the latter is that you get a static check for syntactic correctness, right? I don’t see any other advantage (but that may be enough of an advantage).
Axel
Andreas is suggesting we adopt a convention or "design pattern" for anonymous exports. Dave is suggesting that it should be supported at the syntax level, so you get language-level guarantees rather than wishful thinking.
On 22 April 2013 16:45, Quildreen Motta <quildreen at gmail.com> wrote:
Andreas is suggesting we adopt a convention or "design pattern" for anonymous exports. Dave is suggesting that it should be supported at the syntax level, so you get language-level guarantees rather than wishful thinking.
What guarantees do you have in mind? AFAICT, you get the same guarantees in both cases, because all named module imports are statically checked.
Andreas Rossberg wrote:
On 22 April 2013 15:56, Brendan Eich<brendan at mozilla.com> wrote:
Andreas Rossberg wrote:
Just to be clear, what I suggested was having a single global convention for the name of the 'anonymous' export in every module, say ,"it". No need to know the "internals" of anything. Your Node example, You are imposing a tax on everyone using modules and anonymous export, today a large base of users (modules are predominately anonymous export by counting NPM modules).
Learning extra syntax is a tax, too. As far as Dave's proposal is concerned, it seems higher to me, without any added value.
We need competitive syntax with the NPM approach, I agree. Dave's proposal may entail about the same number of characters (I didn't count) but it can be tuned.
You may be used to "it" conventions in ML, but we're talking about JS here. It's not ML and the communities using modules have already voted, and they will vote with their feet (run away from ES6) if it comes to it.
There are substantial differences between legacy modules and what will be in ES6. People will have to adapt, whatever we do. Drawing the "people running away screaming" card is unhelpful rhetoric as long as no valid technical argument is involved.
What would a valid technical argument look like?
We're talking about usability and convention vs. syntax here. This argument is mostly about developer ergonomics. No proofs.
The logical conclusion of your argument would be to just adopt the union of the existing systems, so that nobody has to change their habits. There is good reason why TC39 didn't choose that route.
Please, no reductio ad absurdum.
The more convincing argument would be interop with those legacy modules. But the magic for that will be in custom loaders, and AFAICS, the problems (and solutions) will likely be the same in either approach.
Yes, custom loaders can interop but as you say, people will have to do something if they want to rewrite to ES6 modules (and Node people may do this soon because they don't have downrev browsers to contend with).
When all those (majority share NPM modules) authors rewrite, what should they have to say? "it", really?
What guarantees do you have in mind? AFAICT, you get the same guarantees in both cases, because all named module imports are statically checked.
I believe Quildreen is referring to the guarantee that everyone use the same name (or in this case, no name at all).
Thanks, Quildreen, for bringing another perspective to the table. You make a valid point, although in the end I think it's overstated. I'm sympathetic to Andreas' argument, but I think he overstates as well (as I have when playing devil's advocate).
For me, it's a toss-up, without much technical risk on either side. I suppose if it makes people happy... : )
Dave, what do you think about the syntax tweaks I presented upthread?
I feel pretty strongly that if we're going to do this, then we should follow Node and make the default "default" the module instance object. That way the "default" represents the chosen "entry point" for the module, and there is always some such "entry point". What do you think?
On Apr 22, 2013, at 5:18 AM, Andreas Rossberg <rossberg at google.com> wrote:
I'm sorry, but I'm afraid this is one of these moments where I have no idea what the heck you guys are talking about. ;)
"The needs of the many outweigh the needs of the few." -- Mr. Spock
www.youtube.com/watch?v=Xa6c3OTr6yA
How is a single name possibly more difficult to learn or remember than any additional piece of syntax, however simple that may be?
We can't create a single, universal name. We can't enforce a naming convention by waggling our eyebrows. We can, by contrast, enforce a universal syntax by specifying it in ES6.
I am constantly, repeatedly met with impassioned requests [*] for anonymous export support by JS developers of multiple communities, e.g., NPM and AMD users alike. Do we wish to just ignore them? Do we favor minor convenience of engine implementors over the readability and clarity of every client of large numbers of idiomatic JavaScript modules?
I am aware that there are a lot of requests from some communities. If they think this is an important feature to have -- in the context of ES6 modules! -- then we should invite them here to make their case.
I believe I have been stating their case (along with others like Quildreen). Your response has not been that my arguments are invalid, but that they are not important enough to warrant an in-language solution. That's not a categorical difference but a difference of degree. We don't vote on es-discuss and I don't think it should take a bunch of "+1" replies for you to believe me that JS users really care about this.
On Apr 22, 2013, at 6:48 AM, Andreas Rossberg <rossberg at google.com> wrote:
(And semantics, I presume, because Dave hasn't actually told us how the "anonymous" export would be distinguished internally.)
Yes I have! I've explained it before, at least at the March meeting and again in passing in this thread. The anonymous export would be available on the module instance object under a standard unique symbol.
For example, you could reflect it from a module instance object like so. I'll arbitrarily use Kevin's syntax for the moment because, well, I have to pick one to make an example.
module "foo" { export default 17; }
import defaultExport from "js/reflect/module"; // presumably the symbol would be available in a stdlib like this import module "foo" as foo; // bind foo to the module instance object of "foo"
console.log(foo[defaultExport]) // 17
On Apr 22, 2013, at 9:43 AM, Kevin Smith <zenparsing at gmail.com> wrote:
Dave, what do you think about the syntax tweaks I presented upthread?
I think it's okay. I could go into detail but I don't think it'll go well in this thread, so I won't.
I feel pretty strongly that if we're going to do this, then we should follow Node and make the default "default" the module instance object. That way the "default" represents the chosen "entry point" for the module, and there is always some such "entry point". What do you think?
It's a bad idea. It forces you into a default export if you don't have one, and makes it a breaking API change if you want to add one. It becomes an attractive nuisance where clients can use it as an alternative for the module binding form (import module
in your syntax), which then makes it impossible for a library author to add a default export later.
It's a bad idea. It forces you into a default export if you don't have one, and makes it a breaking API change if you want to add one. It becomes an attractive nuisance where clients can use it as an alternative for the module binding form (
import module
in your syntax), which then makes it impossible for a library author to add a default export later.
Alas, I'm afraid I'm doomed to have more bad ideas before the day is done! I agree with your points, but find myself tiring of this anonymous hack all over again. I think I'll retire for the present.
On 22 April 2013 17:03, Brendan Eich <brendan at mozilla.com> wrote:
Andreas Rossberg wrote:
There are substantial differences between legacy modules and what will be in ES6. People will have to adapt, whatever we do. Drawing the "people running away screaming" card is unhelpful rhetoric as long as no valid technical argument is involved.
What would a valid technical argument look like?
We're talking about usability and convention vs. syntax here. This argument is mostly about developer ergonomics. No proofs.
Concrete evidence about usability improvements counts fine as a technical argument. But so far, the only concrete evidence I've seen was Kevin demonstrating that anonymous export is not all that relevant given ES6 import syntax. ;)
The more convincing argument would be interop with those legacy modules. But the magic for that will be in custom loaders, and AFAICS, the problems (and solutions) will likely be the same in either approach.
Yes, custom loaders can interop but as you say, people will have to do something if they want to rewrite to ES6 modules (and Node people may do this soon because they don't have downrev browsers to contend with).
When all those (majority share NPM modules) authors rewrite, what should they have to say? "it", really?
You assume that everybody will just want to transplant their NPM-specific modularity conventions to ES6 unchanged. I don't know if that is true. In any case, I don't expect that "default" will make them any happier.
Andreas Rossberg wrote:
In any case, I don't expect that "default" will make them any happier.
Agreed, need more concise syntax for this common case -- but now we're "just arguing about the price^H^H^H^H^Hsyntax" ;-). So you're on board, then?
On 22 April 2013 21:46, David Herman <dherman at mozilla.com> wrote:
On Apr 22, 2013, at 5:18 AM, Andreas Rossberg <rossberg at google.com> wrote:
I'm sorry, but I'm afraid this is one of these moments where I have no idea what the heck you guys are talking about. ;)
"The needs of the many outweigh the needs of the few." -- Mr. Spock
Yeah, except that I never contested that, but rather asked for sufficient evidence (a) for those needs, and (b) that the proposed solution addresses them. So far, I'm afraid I remain unconvinced about either. ;)
On 22 April 2013 22:10, David Herman <dherman at mozilla.com> wrote:
On Apr 22, 2013, at 6:48 AM, Andreas Rossberg <rossberg at google.com> wrote:
(And semantics, I presume, because Dave hasn't actually told us how the "anonymous" export would be distinguished internally.)
Yes I have! I've explained it before, at least at the March meeting and again in passing in this thread. The anonymous export would be available on the module instance object under a standard unique symbol.
Just to be clear, AFAICT, this requires a semantic extension. A module body is, first and foremost, a lexical environment, and environments do not currently have a notion of symbol-named variables (nor should they, IMO).
On 23 April 2013 09:34, Andreas Rossberg <rossberg at google.com> wrote:
On 22 April 2013 17:03, Brendan Eich <brendan at mozilla.com> wrote:
The more convincing argument would be interop with those legacy modules. But the magic for that will be in custom loaders, and AFAICS, the problems (and solutions) will likely be the same in either approach.
Yes, custom loaders can interop but as you say, people will have to do something if they want to rewrite to ES6 modules (and Node people may do this soon because they don't have downrev browsers to contend with).
When all those (majority share NPM modules) authors rewrite, what should they have to say? "it", really?
You assume that everybody will just want to transplant their NPM-specific modularity conventions to ES6 unchanged. I don't know if that is true. In any case, I don't expect that "default" will make them any happier.
/Andreas
What remains to be seen is whether people writing CommonJS/AMD modules today will want to move to ES6 modules at all ;3
On Tue, Apr 23, 2013 at 8:55 AM, Andreas Rossberg <rossberg at google.com> wrote:
On 22 April 2013 22:10, David Herman <dherman at mozilla.com> wrote:
On Apr 22, 2013, at 6:48 AM, Andreas Rossberg <rossberg at google.com> wrote:
(And semantics, I presume, because Dave hasn't actually told us how the "anonymous" export would be distinguished internally.)
Yes I have! I've explained it before, at least at the March meeting and again in passing in this thread. The anonymous export would be available on the module instance object under a standard unique symbol.
Just to be clear, AFAICT, this requires a semantic extension. A module body is, first and foremost, a lexical environment, and environments do not currently have a notion of symbol-named variables (nor should they, IMO).
No, this does not require a semantic extension. I think everyone agrees that environments should not have symbol-named variables. However, this is neither here nor there for module instance objects, which are reflections of module exports as "plain" JS objects. There is no semantic extension required for them to have symbol-named properties.
No, this does not require a semantic extension. I think everyone agrees that environments should not have symbol-named variables. However, this is neither here nor there for module instance objects, which are reflections of module exports as "plain" JS objects. There is no semantic extension required for them to have symbol-named properties.
I think it rather points points to the hack: you propose that a module instance object have a property which does not correspond to some lexical declaration.
On Tue, Apr 23, 2013 at 9:05 AM, Kevin Smith <zenparsing at gmail.com> wrote:
No, this does not require a semantic extension. I think everyone agrees that environments should not have symbol-named variables. However, this is neither here nor there for module instance objects, which are reflections of module exports as "plain" JS objects. There is no semantic extension required for them to have symbol-named properties.
I think it rather points points to the hack: you propose that a module instance object have a property which does not correspond to some lexical declaration.
The properties correspond to exports, not lexical declarations. And the anonymous export is certainly an export.
On 23 April 2013 15:02, Sam Tobin-Hochstadt <samth at ccs.neu.edu> wrote:
On Tue, Apr 23, 2013 at 8:55 AM, Andreas Rossberg <rossberg at google.com> wrote:
On 22 April 2013 22:10, David Herman <dherman at mozilla.com> wrote:
On Apr 22, 2013, at 6:48 AM, Andreas Rossberg <rossberg at google.com> wrote:
(And semantics, I presume, because Dave hasn't actually told us how the "anonymous" export would be distinguished internally.)
Yes I have! I've explained it before, at least at the March meeting and again in passing in this thread. The anonymous export would be available on the module instance object under a standard unique symbol.
Just to be clear, AFAICT, this requires a semantic extension. A module body is, first and foremost, a lexical environment, and environments do not currently have a notion of symbol-named variables (nor should they, IMO).
No, this does not require a semantic extension. I think everyone agrees that environments should not have symbol-named variables. However, this is neither here nor there for module instance objects, which are reflections of module exports as "plain" JS objects. There is no semantic extension required for them to have symbol-named properties.
Then it is a semantic extension simply because default imports and exports cannot be rewritten to plain module syntax, i.e. are not syntactic sugar, right?
On 23 April 2013 14:37, Brendan Eich <brendan at mozilla.com> wrote:
So you're on board, then?
Last year I said that I'd be fine if it was confined to syntactic sugar. And I actually suggested something very similar to Dave's current proposal (see e.g. esdiscuss/2012-December/027008). So we may be getting close. :)
On Tue, Apr 23, 2013 at 9:07 AM, Andreas Rossberg <rossberg at google.com> wrote:
On 23 April 2013 15:02, Sam Tobin-Hochstadt <samth at ccs.neu.edu> wrote:
On Tue, Apr 23, 2013 at 8:55 AM, Andreas Rossberg <rossberg at google.com> wrote:
On 22 April 2013 22:10, David Herman <dherman at mozilla.com> wrote:
On Apr 22, 2013, at 6:48 AM, Andreas Rossberg <rossberg at google.com> wrote:
(And semantics, I presume, because Dave hasn't actually told us how the "anonymous" export would be distinguished internally.)
Yes I have! I've explained it before, at least at the March meeting and again in passing in this thread. The anonymous export would be available on the module instance object under a standard unique symbol.
Just to be clear, AFAICT, this requires a semantic extension. A module body is, first and foremost, a lexical environment, and environments do not currently have a notion of symbol-named variables (nor should they, IMO).
No, this does not require a semantic extension. I think everyone agrees that environments should not have symbol-named variables. However, this is neither here nor there for module instance objects, which are reflections of module exports as "plain" JS objects. There is no semantic extension required for them to have symbol-named properties.
Then it is a semantic extension simply because default imports and exports cannot be rewritten to plain module syntax, i.e. are not syntactic sugar, right?
It can only be rewritten to regular exports provided that the
rewriting can choose an otherwise-unused name. We could define it as
syntactic sugar by simply saying that it uses the element of the set
xx*
with the shortest length that is not otherwise an export of the
module. However, I think we can all agree that such a solution would
be worse than using a well-known symbol. I don't think there's a
meaningful difference in terms of whether it's a semantic extension.
On 23 April 2013 15:31, Sam Tobin-Hochstadt <samth at ccs.neu.edu> wrote:
On Tue, Apr 23, 2013 at 9:07 AM, Andreas Rossberg <rossberg at google.com> wrote:
On 23 April 2013 15:02, Sam Tobin-Hochstadt <samth at ccs.neu.edu> wrote:
On Tue, Apr 23, 2013 at 8:55 AM, Andreas Rossberg <rossberg at google.com> wrote:
On 22 April 2013 22:10, David Herman <dherman at mozilla.com> wrote:
On Apr 22, 2013, at 6:48 AM, Andreas Rossberg <rossberg at google.com> wrote:
(And semantics, I presume, because Dave hasn't actually told us how the "anonymous" export would be distinguished internally.)
Yes I have! I've explained it before, at least at the March meeting and again in passing in this thread. The anonymous export would be available on the module instance object under a standard unique symbol.
Just to be clear, AFAICT, this requires a semantic extension. A module body is, first and foremost, a lexical environment, and environments do not currently have a notion of symbol-named variables (nor should they, IMO).
No, this does not require a semantic extension. I think everyone agrees that environments should not have symbol-named variables. However, this is neither here nor there for module instance objects, which are reflections of module exports as "plain" JS objects. There is no semantic extension required for them to have symbol-named properties.
Then it is a semantic extension simply because default imports and exports cannot be rewritten to plain module syntax, i.e. are not syntactic sugar, right?
It can only be rewritten to regular exports provided that the rewriting can choose an otherwise-unused name. We could define it as syntactic sugar by simply saying that it uses the element of the set
xx*
with the shortest length that is not otherwise an export of the module. However, I think we can all agree that such a solution would be worse than using a well-known symbol. I don't think there's a meaningful difference in terms of whether it's a semantic extension.
Strictly speaking that would not be syntactic sugar either, because the desugaring would be context-dependent in a manner that goes beyond a simple pick-a-fresh-name regime (because it has to be deterministically match on both ends of the export/import edge).
The only way to make it purely syntactic would be by picking an ordinary variable name, once and for all. In other words, codify a convention. ;)
But I agree that this all is a minor point, and playing some symbol magic is close enough.
So we'll need two different productions for importing the "default" vs. importing the module instance object. Even with node core, we'll need to use two different forms:
import "stream" as Stream;
import module "fs" as fs;
// Or choose any other two variations you want...
This is more complicated than what they have now. How do we know the proposed "default" will make developers happy?
Revisiting Quildreen's use case:
import jsonRequestMiddleware from "json-request-middleware";
import jsonOutputMiddleware from "json-output-middleware";
If developers choose, they can adopt a naming convention such that the main export is the "identiferized" version of their published module name, as above. This does not require memorization. It does not require understanding the internal structure of the target. It does not require renaming.
Or they may choose some other naming convention. Demonizing naming conventions is just plain silly.
I think Quildreen provides an argument, no so much for default exports, but against declarative-binding modules. I think we should consider the possibility that it will be impossible to make developers "happy" with a module system that is, by its very nature, less flexible than the dynamic one that they currently have.
More thoughts tomorrow. For now, to sleep!
Kevin Smith wrote:
Or they may choose some other naming convention. Demonizing naming conventions is just plain silly.
No one demonized naming conventions. The fact is not having to agree on a name is one less thing to hassle with, that's all.
I think Quildreen provides an argument, no so much for default exports, but against declarative-binding modules.
No, that goes too far.
I think we should consider the possibility that it will be impossible to make developers "happy" with a module system that is, by its very nature, less flexible than the dynamic one that they currently have.
That's certainly true, but so what? We can't have modules-as-objects and synchronous require in browsers (Node cheats there). We're doing modules to fill gaps in the language that objects and functions can't fill.
Currently, curly braces are used within import and export declarations:
import { something } from "url"; import { something: renamed } from "url"; import { a, b, c } from "url"; export { something }; export { renamed: something }; export { a, b, c };
The
import
andexport
curlies are analogies for destructing and object literal syntax, respectively. This analogy is problematic for the following reasons:Destructuring declarations create new variables, whereas import declarations create aliases for existing variables. This distinction is subtle and can lead to misunderstandings about module semantics.
Beginners tend to get the "direction" of renaming wrong with destructuring syntax:
let { myX: x } = { x: "value" }; // Oops!
Since destructuring is an intermediate to advanced feature, it's not a big problem in that context. But importing is one of the most basic features in the language. We don't want to have a stumbling block right at the front door.
// Since is ok: export { exportedA: a }; // It leads one to believe that this is ok: export { exportedA: "some-value" };
Given those considerations, I think we should find a curly-free syntax for import and export declarations. Here is a proposal:
import something from "url"; import something as renamed from "url"; import a, b, c from "url"; export something; export something as renamed; export a, b, c;
In the above syntax, "as" becomes the unified keyword for renaming module bindings.
For an analysis of the ASI issues, see: gist.github.com/zenparsing/5395635