simpler, sweeter syntax for modules

# David Herman (12 years ago)

H'lo,

Thanks to some a-maz-ing [1] work by Andreas Rossberg (I'll spare the gory algorithmic details), the linking process no longer needs the syntactic distinction that static module bindings are only created via the module (contextual) keyword. This frees us up to simplify the syntax, making much less use of the module keyword and much more use of import.

I've drafted a new syntax that is both much simpler and, I think, far more intuitive than the previous version.

Examples:

  • importing an external module:

    import "foo.js" as Foo;

  • importing a module's export:

    import foo from "foo.js"; import bar from Bar; import baz from bar.mumble.quux;

  • importing all exports:

    import * from Bar;

  • importing with renaming:

    import { draw: drawGun } from "cowboy.js", { draw: drawWidget } from "widgets.js";

  • defining a module (same as ever):

    module m { ... }

  • aliasing a module for convenience:

    module m = foo.bar.baz;

This new syntax is up on the wiki:

http://wiki.ecmascript.org/doku.php?id=harmony:modules

A couple conventions to note about this:

  • the "as" contextual keyword signifies renaming the module itself

  • the "from" contextual keyword always signifies extracting an export /from/ the module

  • while "import" no longer strictly means extracting an export, it matches common spoken usage better -- "import" is used both to mean extracting exports /and/ loading external modules

  • despite this overloading, a single "import" declaration is only ever importing modules or importing bindings, never both

Comments welcome [2], Dave

[1] Really. You have no idea. [2] Translation: Unleash the hounds of bikeshedding! ;

# Luke Hoban (12 years ago)

Dave -

Great to see the updates. A couple of questions:

import "foo.js" as Foo; import foo from "foo.js";

These two forms look rather confusingly similar given how different they are, and the inversion of order of where the filename lives doesn't seem to line up with the semantic difference between the two forms. It feels like this is an attempt towards fluent syntax design, but to me at least, this just doesn't feel easy to learn/remember/read.

module m = foo.bar.baz;

Why isn't aliasing a remote module the same as aliasing a local module? That is, why can't I say 'module m = "foo.js"' instead of 'import "foo.js" as m'? It seems the whole syntactic surface area of modules would be simpler if remote module identifiers were used in the same way as local module bindings in the various syntactic forms. Since this seems to be the way things are done on the RHS of 'import...from...', why be different in 'module...=...'? I thought this is how things were originally - why change?

More broadly - is it possible to reduce further to just 'module...=...' and 'import...from...'? Which mean "alias a module" and "import names from inside a module" respectively.

Luke

# Axel Rauschmayer (12 years ago)

I like it, more in line with AMDs and Node.js modules.

Honest question: Are nested modules really needed?

# Axel Rauschmayer (12 years ago)

Following Luke’s argument: Everything is modeled after an assignment (roughly: lhs is the new identifier, rhs is the definition) except for import "foo.js" as Foo;

Other possibilities (not sure assigning a string makes sense): module Foo is "foo.js"; module Foo from "foo.js"; module Foo via "foo.js";

# Kris Kowal (12 years ago)

On Wed, Mar 21, 2012 at 4:05 PM, Axel Rauschmayer <axel at rauschma.de> wrote:

Honest question: Are nested modules really needed?

There is a chance that they would be useful for bundling. Modules can’t be concatenated.

Kris Kowal

# David Herman (12 years ago)

On Mar 21, 2012, at 3:44 PM, Luke Hoban wrote:

Great to see the updates. A couple of questions:

import "foo.js" as Foo; import foo from "foo.js";

These two forms look rather confusingly similar given how different they are, and the inversion of order of where the filename lives doesn't seem to line up with the semantic difference between the two forms. It feels like this is an attempt towards fluent syntax design, but to me at least, this just doesn't feel easy to learn/remember/read.

Yeah, I'm not trying to do fluent syntax per se; the goal is not COBOL/AppleScript. But it should still read well, in such a way as to avoid the off-by-one confusion inherent in being able to import a module or import its exports.

Andreas also didn't like the inversion of order. I came to this because earlier versions of the syntax were inconsistent about whether from meant the-module-itself or an-export-of-the-module, which made it even more confusing. This new syntax was inspired by Python, but with import as the leading keyword, rather than from. Which is what leads to the inversion of order.

I've gone through a few alternatives, but they all read more awkwardly to me:

import Foo at "foo.js"; // ...maybe? *shrug*
import Foo is "foo.js"; // reads awkwardly
module Foo = "foo.js";  // looks like Foo is a string
module Foo is "foo.js"; // just looks like an awkward workaround

module m = foo.bar.baz;

Why isn't aliasing a remote module the same as aliasing a local module? That is, why can't I say 'module m = "foo.js"' instead of 'import "foo.js" as m'? It seems the whole syntactic surface area of modules would be simpler if remote module identifiers were used in the same way as local module bindings in the various syntactic forms. Since this seems to be the way things are done on the RHS of 'import...from...', why be different in 'module...=...'? I thought this is how things were originally - why change?

Perhaps. I saw community members upset that we would use module as the keyword for importing external modules instead of import, e.g.:

https://twitter.com/#!/substack/status/170161863814946816

Moreover, Brendan felt that module...=..."..." looks too confusingly like you're assigning a string value. Others I spoke to, particularly those accustomed to Python, felt that import read better as the way to load external modules.

More broadly - is it possible to reduce further to just 'module...=...' and 'import...from...'? Which mean "alias a module" and "import names from inside a module" respectively.

Certainly possible, yes. I think that's Andreas's preferred syntax as well. I think that's a viable alternative. Just based on my anecdotal experience talking to people so far, I suspect we'd find people's preferences split. The good news is that we are no longer constrained by technical details of the linking semantics as to which way we want to go. The bad news is that we'll have to pick a syntax, and some people will undoubtedly hate it. :-P

Seriously, I don't feel religious about either of the two syntaxen. I think I will put up both alternatives on the wiki for a better head-to-head comparison.

Thanks for the feedback,

# Kevin Smith (12 years ago)

This looks really nice!

# David Herman (12 years ago)

On Mar 21, 2012, at 4:25 PM, Kris Kowal wrote:

On Wed, Mar 21, 2012 at 4:05 PM, Axel Rauschmayer <axel at rauschma.de> wrote:

Honest question: Are nested modules really needed?

There is a chance that they would be useful for bundling. Modules can’t be concatenated.

Yeah, I believe that's the strongest argument for nested modules.

It's also really nice for simple, lightweight grouping of components of a library.

Another issue if you can't nest modules would be restricting what it means to bind a module that you load from elsewhere inside another module, since in some sense that's a nested module. Maybe you just say you can't export those, or maybe they're only exportable as values.

But anyway, nesting modules is just more flexible and expressive. And yeah, the bundling point is probably the most important in practice.

# David Herman (12 years ago)

Follow-up: I've factored out these two alternative approaches, both of which I think are defensible.

Grammars:

http://wiki.ecmascript.org/doku.php?id=harmony:modules#syntax

Simple examples:

http://wiki.ecmascript.org/doku.php?id=harmony:modules#external_module_load
# Axel Rauschmayer (12 years ago)

I like variant B, because it follows the rules:

  • "module" => define a module
  • "import" => extract something out of a module

But I would use a keyword instead of "=" (due to the reason that you mentioned). Compare:

 module Bar = "bar.js";
 module Bar is "bar.js";
 module Bar from "bar.js";
 module Bar in "bar.js";
 module Bar via "bar.js";

I’m not entirely happy with either one of these keywords, but they all seem better to me than the equals sign. So any other keyword is fine by me, really.

# John J Barton (12 years ago)

On Wed, Mar 21, 2012 at 9:01 PM, Axel Rauschmayer <axel at rauschma.de> wrote:

I like variant B, because it follows the rules:

  • "module" => define a module
  • "import" => extract something out of a module

But I would use a keyword instead of "=" (due to the reason that you mentioned). Compare:

module Bar = "bar.js";     module Bar is "bar.js";     module Bar from "bar.js";     module Bar in "bar.js";     module Bar via "bar.js";

I’m not entirely happy with either one of these keywords, but they all seem better to me than the equals sign. So any other keyword is fine by me, really.

equals makes sense when it is assigment:

module Bar = load("bar.js");

jjb

# Brendan Eich (12 years ago)

David Herman wrote:

On Mar 21, 2012, at 3:44 PM, Luke Hoban wrote:

Great to see the updates. A couple of questions:

import "foo.js" as Foo; import foo from "foo.js"; [snip]

Andreas also didn't like the inversion of order. I came to this because earlier versions of the syntax were inconsistent about whether from meant the-module-itself or an-export-of-the-module, which made it even more confusing. This new syntax was inspired by Python, but with import as the leading keyword, rather than from. Which is what leads to the inversion of order.

Perhaps Python has the right syntax, then?

from "foo.js" import foo; import "foo.js" as Foo;

I'm not wed to any syntax but if the only problem is the swapping of imported export patterns and the module reference or MRL, then we could fix that just as Python did.

# Brendan Eich (12 years ago)

David Herman wrote:

On Mar 21, 2012, at 3:44 PM, Luke Hoban wrote:

Great to see the updates. A couple of questions:

import "foo.js" as Foo; import foo from "foo.js"; [snip]

Andreas also didn't like the inversion of order. I came to this because earlier versions of the syntax were inconsistent about whether from meant the-module-itself or an-export-of-the-module, which made it even more confusing. This new syntax was inspired by Python, but with import as the leading keyword, rather than from. Which is what leads to the inversion of order.

Perhaps Python has the right syntax, then?

from "foo.js" import foo; import "foo.js" as Foo;

I'm not wed to any syntax but if the only problem is the swapping of imported export patterns and the module reference or MRL, then we could fix that just as Python did.

# David Herman (12 years ago)

On Mar 21, 2012, at 9:01 PM, Axel Rauschmayer wrote:

I like variant B, because it follows the rules:

  • "module" => define a module
  • "import" => extract something out of a module

Sure, but note that you can import a sub-module, so it's not a totally clean split.

But I would use a keyword instead of "=" (due to the reason that you mentioned). Compare:

module Bar = "bar.js";
module Bar is "bar.js";
module Bar from "bar.js";
module Bar in "bar.js";
module Bar via "bar.js";

I’m not entirely happy with either one of these keywords, but they all seem better to me than the equals sign. So any other keyword is fine by me, really.

I think the = sign looks the cleanest.

No "from" -- we have to be consistent that "from" only means extraction. The others all just look awkward.

# David Herman (12 years ago)

On Mar 21, 2012, at 9:28 PM, John J Barton wrote:

equals makes sense when it is assigment:

module Bar = load("bar.js");

It's not an assignment, though. Which is why Brendan didn't like it in the first place, since he felt programmers would get confused that it was a dynamic assignment expression statement.

OTOH, this confusion can't exactly last long, when the parser won't even parse your program. I'm not sure that confusion is really worth worrying about.

# David Herman (12 years ago)

On Mar 21, 2012, at 9:41 PM, Brendan Eich wrote:

Perhaps Python has the right syntax, then?

from "foo.js" import foo; import "foo.js" as Foo;

I suppose we could. I always thought this was an awkward choice on Python's part. The first line always reminds me of the Yinglish constructions my grandma used to joke about from her childhood ("cut me up and butter me and throw me down the baby a piece of bread!").

I'm not wed to any syntax but if the only problem is the swapping of imported export patterns and the module reference or MRL, then we could fix that just as Python did.

The thing is, I just don't really see the argument against the bound variable being on the RHS. It reads perfectly naturally:

import "foo.js" as foo;

I don't see anything hard to follow about that. There's no possible alternative meaning you could ascribe to it than the right one.

# Brendan Eich (12 years ago)

David Herman wrote:

On Mar 21, 2012, at 9:28 PM, John J Barton wrote:

equals makes sense when it is assigment:

module Bar = load("bar.js");

It's not an assignment, though. Which is why Brendan didn't like it in the first place, since he felt programmers would get confused that it was a dynamic assignment expression statement.

OTOH, this confusion can't exactly last long, when the parser won't even parse your program. I'm not sure that confusion is really worth worrying about.

But why make misleading syntax? More of a question for John: why write |load("bar.js")| there, looking for all the world like a function call evaluated in order at runtime, when this is a special form evaluated before runtime?

# Gavin Barraclough (12 years ago)

On Mar 21, 2012, at 3:28 PM, David Herman wrote:

  • importing with renaming:

    import { draw: drawGun } from "cowboy.js", { draw: drawWidget } from "widgets.js";

Hey Dave,

This all looks really nice! - the only thing that threw me was the export rename – I intuitively expect the think on the left of the colon to be the new name, not the old one. I guess my expectation comes from existing JS: :-)

var oldName = 42; var o = { newName: oldName };

The contextual 'as' looks nice to me for module renames, and would make the direction of the renaming clear - was this been considered for the exports too?

import { draw as drawGun } from "cowboy.js", { draw as drawWidget } from "widgets.js";

Also, are the braces necessary to parse this? Perhaps they could be omitted?

import draw as drawGun from "cowboy.js";

# Peter van der Zee (12 years ago)

On Wed, Mar 21, 2012 at 11:28 PM, David Herman <dherman at mozilla.com> wrote:

  • importing with renaming:

import { draw: drawGun }    from "cowboy.js",           { draw: drawWidget } from "widgets.js";

The brackets don't seem necessary (at least not from a parsing perspective). Maybe drop them?

import draw: drawGun    from "cowboy.js",       draw: drawWidget from "widgets.js";

If you fear that's too confusing with guards, replace the colon for an "arrow" (=>) or whatever. The bracket syntax is pretty confusing with

object literal notation imo.

# Andreas Rossberg (12 years ago)

On 22 March 2012 10:23, Peter van der Zee <ecma at qfox.nl> wrote:

On Wed, Mar 21, 2012 at 11:28 PM, David Herman <dherman at mozilla.com> wrote:

  • importing with renaming:

    import { draw: drawGun } from "cowboy.js", { draw: drawWidget } from "widgets.js";

The brackets don't seem necessary (at least not from a parsing perspective). Maybe drop them?

import draw: drawGun from "cowboy.js", draw: drawWidget from "widgets.js";

If you fear that's too confusing with guards, replace the colon for an "arrow" (=>) or whatever. The bracket syntax is pretty confusing with object literal notation imo.

It shouldn't be too confusing, and the similarity is in fact intentional. You should read import as destructuring (and export as the inverse structuring, if you want).

# Herby Vojčík (12 years ago)

David Herman wrote:

On Mar 21, 2012, at 9:28 PM, John J Barton wrote:

equals makes sense when it is assigment:

module Bar = load("bar.js");

It's not an assignment, though. Which is why Brendan didn't like it in the first place, since he felt programmers would get confused that it was a dynamic assignment expression statement.

What about just a whitespace then:

module Bar "bar.js";

# Andreas Rossberg (12 years ago)

On 22 March 2012 02:35, David Herman <dherman at mozilla.com> wrote:

Follow-up: I've factored out these two alternative approaches, both of which I think are defensible.

Grammars:

harmony:modules#syntax

Nice! One question: is there a reason why you not just define

Program ::= ModuleBody

?

Besides that, you already mentioned my gripes with variant A (pretty much exactly what Luke said), so I don't have to repeat them. Obviously, that makes me favour variant B. :)

# Andreas Rossberg (12 years ago)

On 22 March 2012 07:12, David Herman <dherman at mozilla.com> wrote:

On Mar 21, 2012, at 9:01 PM, Axel Rauschmayer wrote:

I like variant B, because it follows the rules:

  • "module" => define a module
  • "import" => extract something out of a module

As Dave already mentioned, that is my stance, too. Despite what newcomers might think initially, I think this is cleaner and ultimately less confusing because it is a better match for the actual semantics.

Sure, but note that you can import a sub-module, so it's not a totally

clean split.

True, but 'import' overlaps with 'module' just as it overlaps with e.g. 'let' or 'const' in that respect.

But I would use a keyword instead of "=" (due to the reason that you mentioned). Compare:

module Bar = "bar.js";
module Bar is "bar.js";
module Bar from "bar.js";
module Bar in "bar.js";
module Bar via "bar.js";

I’m not entirely happy with either one of these keywords, but they all seem better to me than the equals sign. So any other keyword is fine by me, really.

I think the = sign looks the cleanest.

No "from" -- we have to be consistent that "from" only means extraction. The others all just look awkward.

Missing from the list is

module Bar at "bar.js"

which was shortlisted for a while as an alternative to '=' for external bindings.

# Andreas Rossberg (12 years ago)

On 22 March 2012 07:33, Brendan Eich <brendan at mozilla.org> wrote:

David Herman wrote:

On Mar 21, 2012, at 9:28 PM, John J Barton wrote:

equals makes sense when it is assigment:

module Bar = load("bar.js");

It's not an assignment, though. Which is why Brendan didn't like it in the first place, since he felt programmers would get confused that it was a dynamic assignment expression statement.

OTOH, this confusion can't exactly last long, when the parser won't even parse your program. I'm not sure that confusion is really worth worrying about.

But why make misleading syntax? More of a question for John: why write |load("bar.js")| there, looking for all the world like a function call evaluated in order at runtime, when this is a special form evaluated before runtime?

I still wonder why you think it is so confusing to use the equality sign for module bindings. It is overloaded to mean "define as" and "assign" in almost all language that use it for the latter, and for different semantic categories, too. I never witnessed anybody being confused by that.

I agree, though, that '=' is somewhat weird with a plain string on the RHS (but so is 'is'). Personally, I could get used to that, but maybe some variant with 'at' is more appropriate.

# Andreas Rossberg (12 years ago)

One point that I haven't seen mentioned yet (and it is unrelated to syntax): "export call" makes me cringe -- making modules callable is not just weird, it seems completely future-hostile to the possibility of having parameterized modules. I don't think it is worth bending the notion of module like that if all that is gained is minor convenience.

# brad dunbar (12 years ago)

This is great stuff. I've got a quick question about the as keyword.

import 'foo.js' as Foo;

Is there any reason we can't simply do

var Foo = import 'foo.js';

When looking for the source of a variable, I often search for /Foo =/ to discern it's origin and it only seems natural that giving a name to module would use an assignment. Furthermore, when combined with a destructuring assignment it seems to simplify importing a modules export. For instance

import foo from "foo.js";

can become

{foo} = import 'foo.js';

I could even see renaming being done in a similar fashion.

-brad dunbar

# Domenic Denicola (12 years ago)

Callable modules are a hugely important use case. jQuery and Underscore spring to mind. I’ve made one myself 1.

Also important: what about construct-able modules? Or to put it another way, what about exporting a module that is a class? Examples from Node 2, 3 and Node-land 4, 5 are abundant.

It makes me think a more generic mechanism would be required to export any arbitrary object as the module itself. Perhaps

export module as function jQuery(selector, context) { … } // or function jQuery(selector, context) { … } export module as jQuery;

export module as class Runnable { … } // or class Runnable { … } export module as Runnable;

From: es-discuss-bounces at mozilla.org [mailto:es-discuss-bounces at mozilla.org] On Behalf Of Andreas Rossberg Sent: Thursday, March 22, 2012 07:55 To: es-discuss Subject: Re: simpler, sweeter syntax for modules

One point that I haven't seen mentioned yet (and it is unrelated to syntax): "export call" makes me cringe -- making modules callable is not just weird, it seems completely future-hostile to the possibility of having parameterized modules. I don't think it is worth bending the notion of module like that if all that is gained is minor convenience.

# Kevin Smith (12 years ago)

Correct me if I have any of this wrong, but modules don't need to be callable for the jQuery use case:

// jquery.js
export function $(...) { ... }

// program.js
import $ from "path/to/jquery.js";

$("#elem").whatever

We don't need "node style" modules-as-classes either:

// MyClass.js
export function MyClass() { ... }

// program.js
import MyClass from "MyClass.js";
new MyClass;

or more succinctly:

// program.js
import * from "MyClass.js";
new MyClass;
# David Herman (12 years ago)

On Mar 22, 2012, at 4:55 AM, Andreas Rossberg wrote:

One point that I haven't seen mentioned yet (and it is unrelated to syntax): "export call" makes me cringe -- making modules callable is not just weird, it seems completely future-hostile to the possibility of having parameterized modules. I don't think it is worth bending the notion of module like that if all that is gained is minor convenience.

Making modules callable has serious semantic warts as well. In order to support static binding, we have to ensure that the properties of a module instance object are statically known, which means that there can't be a mutable prototype in the prototype chain. But to support the expected .bind/.call/.apply methods of functions, that means we have to a) pollute callable modules with these extra inherited properties and b) freeze the prototype. This is all pretty ad hoc and unsatisfying.

I would also be in favor of holding the line against callable modules, but a number of people have stated that they consider them very important. One of the going examples has been jQuery, where you would say:

// jquery.js
export call(selector) { ... }
...

// client
import "jquery.js" as $; /*OR*/ module $ = "jquery.js" /*OR*/ module $ at "jquery.js" /*OR*/ import $ at "jquery.js"

let elements = ["foo", "bar", "baz"].map($); // implicitly calls .call
$("quux").style(...);

That said, it requires no more code to say:

// jquery.js
export function $(selector) { ... }
...

// client
import $ from "jquery.js";

let elements = ["foo", "bar", "baz"].map($);
$("quux").style(...);

So maybe people have over-sold the importance of callable modules. In CommonJS + ES5, it's convenient because there's no way to avoid binding a name to the module. Whereas if you have destructuring and destructuring import, a module with a single export does not need to be any less convenient than a module that is itself the single export.

Thoughts?

# Russell Leggett (12 years ago)

That said, it requires no more code to say:

// jquery.js export function $(selector) { ... } ...

// client import $ from "jquery.js";

let elements = ["foo", "bar", "baz"].map($); $("quux").style(...);

So maybe people have over-sold the importance of callable modules. In CommonJS + ES5, it's convenient because there's no way to avoid binding a name to the module. Whereas if you have destructuring and destructuring import, a module with a single export does not need to be any less convenient than a module that is itself the single export.

Thoughts?

Yeah, I think you hit the nail on the head, there. With the destructuring import, callable modules is pointless complexity. It stays simple and intuitive IMO.

# David Herman (12 years ago)

On Mar 22, 2012, at 4:46 AM, Andreas Rossberg wrote:

Nice! One question: is there a reason why you not just define

Program ::= ModuleBody

?

No exports from a Program.

# David Herman (12 years ago)

On Mar 22, 2012, at 4:48 AM, Andreas Rossberg wrote:

On 22 March 2012 07:33, Brendan Eich <brendan at mozilla.org> wrote:

But why make misleading syntax? More of a question for John: why write |load("bar.js")| there, looking for all the world like a function call evaluated in order at runtime, when this is a special form evaluated before runtime?

Making it look like a function call is particularly bad. The assignment to the literal is, IMO, much less confusing.

I still wonder why you think it is so confusing to use the equality sign for module bindings. It is overloaded to mean "define as" and "assign" in almost all language that use it for the latter, and for different semantic categories, too. I never witnessed anybody being confused by that.

I agree, though, that '=' is somewhat weird with a plain string on the RHS (but so is 'is'). Personally, I could get used to that, but maybe some variant with 'at' is more appropriate.

The 'at' might be appropriate but it just looks like an unfinished thought: "the module foo at 'foo.js'" ...what? Both the import-as and module-= forms state what the declaration is doing. The former declares it is importing the module; the latter declares it is defining/assigning the module. Either is sensible.

# John J Barton (12 years ago)

On Wed, Mar 21, 2012 at 11:33 PM, Brendan Eich <brendan at mozilla.org> wrote:

David Herman wrote:

On Mar 21, 2012, at 9:28 PM, John J Barton wrote:

equals makes sense when it is assigment:

module Bar = load("bar.js");

It's not an assignment, though. Which is why Brendan didn't like it in the first place, since he felt programmers would get confused that it was a dynamic assignment expression statement.

OTOH, this confusion can't exactly last long, when the parser won't even parse your program. I'm not sure that confusion is really worth worrying about.

But why make misleading syntax? More of a question for John: why write |load("bar.js")| there, looking for all the world like a function call evaluated in order at runtime, when this is a special form evaluated before runtime?

I guess you mean: a special form evaluated before the outer function runs? Surely this form is not off-line.

What kinds of mistakes would I make? Seem to me the code has to work as if there was a function call. The only 'special' thing is that the compiler may decide to fetch and compile bar.js before the function containing the module statement.

One thing I could not determine from the strawman is what happens in this case:

if (version === 1) { import "foo1.js" as Foo; } else { import 'foo2.js' as Foo; }

Assume that compiling fooN in the wrong environment fails badly.

jjb

# Andreas Rossberg (12 years ago)

On 22 March 2012 16:30, David Herman <dherman at mozilla.com> wrote:

On Mar 22, 2012, at 4:46 AM, Andreas Rossberg wrote:

Nice! One question: is there a reason why you not just define

Program ::= ModuleBody

?

No exports from a Program.

Except that the syntax currently includes that. ;-)

I assumed this is because you want to parse an external module as a program.

Btw, another question that has bugged me lately: with "1JS", what happens when I do a sloppy-mode direct eval on a program string containing a module declaration? In global scope? In local scope?

# Luke Hoban (12 years ago)

The bracket syntax is pretty confusing with object literal notation imo.

It shouldn't be too confusing, and the similarity is in fact intentional. You should read import as destructuring (and export as the inverse structuring, if you want).

I agree that this isn't really a question specifically for modules, as they are just reusing the destructuring syntax. But I will say that I've seen a lot of confusion about the object destructuring syntax generally. At least half the times I've shown object destructuring or module import renaming syntax I've gotten "don't you have that backwards?" remarks. I can't say I have a better recommendation for the general object destructuring syntax, but I expect this to be a point of significant confusion for newcomers to ES6.

Luke

# Brendan Eich (12 years ago)

Andreas Rossberg wrote:

I still wonder why you think it is so confusing to use the equality sign for module bindings. It is overloaded to mean "define as" and "assign" in almost all language that use it for the latter, and for different semantic categories, too. I never witnessed anybody being confused by that.

My point is simple:

let a = "hi"; const k = "there"; module m = "haha";

"One of these things is not like the other, one of these things just doesn't belong."

Yes, we could make a just-so story that is consistent, and teach it to people. But can we do better?

I agree, though, that '=' is somewhat weird with a plain string on the RHS (but so is 'is'). Personally, I could get used to that, but maybe some variant with 'at' is more appropriate.

Yup,

module m at "lol/m.js";

seems much better.

# Brendan Eich (12 years ago)

David Herman wrote:

The 'at' might be appropriate but it just looks like an unfinished thought: "the module foo at 'foo.js'" ...what? Both the import-as and module-= forms state what the declaration is doing. The former declares it is importing the module; the latter declares it is defining/assigning the module. Either is sensible.

But (small objection, last time from me) the seeming use of an Iniitaliser (from the grammar), i.e., '= <expr>' -- even with

constraints on <expr> -- is a kind of pun, potentially confusing or just

awkward.

You make a good case for import-as!

# Brendan Eich (12 years ago)

John J Barton wrote:

I guess you mean: a special form evaluated before the outer function runs? Surely this form is not off-line.

No, before anything in the containing Program (up to the <script>

container) runs. Modules are prefetched, not loaded by a nested event loop that violates run-to-completion (or unthinkable: starves the same-page-context UI to preserve r-t-c by queueing events).

# Allen Wirfs-Brock (12 years ago)

On Mar 22, 2012, at 9:12 AM, Luke Hoban wrote:

The bracket syntax is pretty confusing with object literal notation imo.

It shouldn't be too confusing, and the similarity is in fact intentional. You should read import as destructuring (and export as the inverse structuring, if you want).

I agree that this isn't really a question specifically for modules, as they are just reusing the destructuring syntax. But I will say that I've seen a lot of confusion about the object destructuring syntax generally. At least half the times I've shown object destructuring or module import renaming syntax I've gotten "don't you have that backwards?" remarks. I can't say I have a better recommendation for the general object destructuring syntax, but I expect this to be a point of significant confusion for newcomers to ES6.

I also find myself getting it backwards, but it helps me to remember that for destructing assignment the target needs to be an arbitrary LHS which won't really fit well to the right of the :

eg

{x: x[n].setter, y: f().A.B.C.Y} = {x:1,y:2};

The following seems worse:

{x[n].setter:x, y: f().A.B.C.Y:y} = aPoint;

# Brendan Eich (12 years ago)

Luke Hoban wrote:

At least half the times I've shown object destructuring or module import renaming syntax I've gotten "don't you have that backwards?" remarks. I can't say I have a better recommendation for the general object destructuring syntax, but I expect this to be a point of significant confusion for newcomers to ES6.

We've rehashed this a lot, so I'm going to rehash a point that helps: most object patterns do not need to rename, so use the shorthand:

let {x, y} = somePoint;

The renaming case as you say cannot reverse property name and binding name (or general LHS in assignments rather than declarations) roles. People will learn. Mozillans have (we didn't add the short-hand until years after adding destructuring in 2006).

Destructuring is quite popular in Mozilla XUL JS. See, e.g., Armin Ronacher's tweet, retweeted by Bob Ippolito:

twitter.com/#!/mitsuhiko/status/181023710810091521

twitter.theinfo.org/181023710810091521#id181054978247491585

This is not worth rehashing again, rather, it's something to document and teach.

# Brendan Eich (12 years ago)

Allen Wirfs-Brock wrote:

I also find myself getting it backwards, but it helps me to remember that for destructing assignment the target needs to be an arbitrary LHS which won't really fit well to the right of the :

You mean left, not right. ;-)

# Allen Wirfs-Brock (12 years ago)

On Mar 22, 2012, at 10:56 AM, Brendan Eich wrote:

Allen Wirfs-Brock wrote:

I also find myself getting it backwards, but it helps me to remember that for destructing assignment the target needs to be an arbitrary LHS which won't really fit well to the right of the :

You mean left, not right. ;-)

See, I didn't keep those R and L long enough on the back of my hands in preschool. (Actually, they didn't really have preschool then, maybe it's showing)

# John J Barton (12 years ago)

On Thu, Mar 22, 2012 at 10:32 AM, Brendan Eich <brendan at mozilla.org> wrote:

John J Barton wrote:

I guess you mean:  a special form evaluated before the outer function runs? Surely this form is not off-line.

No, before anything in the containing Program (up to the <script> container) runs. Modules are prefetched, not loaded by a nested event loop that violates run-to-completion (or unthinkable: starves the same-page-context UI to preserve r-t-c by queueing events).

I realize I mixed up two different things.

First developers need to understand the implications of import "foo1.js" as Foo; You say they are not at all like var Foo = load('foo1.js"); and the syntax should differ. I sure agree with that.

Second, we need a solution for asynchronous loading with run-time selection. We use it now and as we move to much better network layers we will use it a lot more.

I don't really understand what you are saying above. Blocking the initial page rendering is not on anyone list of needs. On the other hand blocking computation on network is not at all unthinkable, but rather it is the standard practice. This should be a first class part of the module solution.

jjb

# David Herman (12 years ago)

On Mar 22, 2012, at 9:06 AM, Andreas Rossberg wrote:

On 22 March 2012 16:30, David Herman <dherman at mozilla.com> wrote:

No exports from a Program.

Except that the syntax currently includes that. ;-)

Oops. Fixed.

I assumed this is because you want to parse an external module as a program.

No, it's because I am an idiot.

Btw, another question that has bugged me lately: with "1JS", what happens when I do a sloppy-mode direct eval on a program string containing a module declaration? In global scope? In local scope?

It's a good question, and one I've thought about some. This could use some whiteboard time -- let's discuss next week when you're in town.

# Brendan Eich (12 years ago)

John J Barton wrote:

Second, we need a solution for asynchronous loading with run-time selection. We use it now and as we move to much better network layers we will use it a lot more.

Of course. This is what the harmony:module_loaders API is all about.

I don't really understand what you are saying above. Blocking the initial page rendering is not on anyone list of needs. On the other hand blocking computation on network is not at all unthinkable, but rather it is the standard practice.

Nope.

This should be a first class part of the module solution.

I don't know what you mean here. First class meaning modules reflect as objects? But that has nothing to do with blocking vs. non-blocking i/o. No blocking i/o apart from sync XHR (a botch to be killed) on the client side. Even sync filesystem (local storage) i/o causes jank.

This is why JS APIs have callbacks, generally, and closures help write callback-based non-blocking i/o multiplexors.

# John J Barton (12 years ago)

On Thu, Mar 22, 2012 at 1:54 PM, Brendan Eich <brendan at mozilla.org> wrote:

John J Barton wrote:

Second, we need a solution for asynchronous loading with run-time selection. We use it now and as we move to much better network layers we will use it a lot more.

Of course. This is what the harmony:module_loaders API is all about.

I don't really understand what you are saying above. Blocking the initial page rendering is not on anyone list of needs.  On the other hand blocking computation on network is not at all unthinkable, but rather it is the standard practice.

Nope.

Some how we are not communicating. I'm talking about AJAX based systems. It's all about blocking computation on network.

This should be a first class part of the module solution.

I don't know what you mean here. First class meaning modules reflect as objects? But that has nothing to do with blocking vs. non-blocking i/o. No blocking i/o apart from sync XHR (a botch to be killed) on the client side. Even sync filesystem (local storage) i/o causes jank.

This is why JS APIs have callbacks, generally, and closures help write callback-based non-blocking i/o multiplexors.

and this is why require.js uses callbacks. We need a module solution that explicitly helps dynamically load source code, not one that forces ad-hoc solutions. We already have that.

The module solution posted seems to have a top-notch solution for the static case but the dynamic case is buried in the loader.

jjb

# Sam Tobin-Hochstadt (12 years ago)

On Thu, Mar 22, 2012 at 5:11 PM, John J Barton <johnjbarton at johnjbarton.com> wrote:

The module solution posted seems to have a top-notch solution for the static case but the dynamic case is buried in the loader.

I don't really understand what you mean by "buried in the loader".

Here's the dynamic case:

let $; system.load("code.jquery.com/jquery-1.7.2.js", function(m) { $ = m; });

Is there something more that you're looking for here?

# Claus Reinke (12 years ago)

Btw, another question that has bugged me lately: with "1JS", what happens when I do a sloppy-mode direct eval on a program string containing a module declaration? In global scope? In local scope?

It's a good question, and one I've thought about some. This could use some whiteboard time -- let's discuss next week when you're in town.

Would this be a good time to mention my previous questions?

modules: import hiding, and usage patterns
https://mail.mozilla.org/pipermail/es-discuss/2012-February/020420.html

supporting ES upgrading with a programming pattern repo?
https://mail.mozilla.org/pipermail/es-discuss/2011-November/018240.html

Previously, I've been told that the time isn't ripe for such discussions. Like some others, I'm concerned about the (relatively) low profile of modules in discussions here.

There are groups who like the static aspects, groups who think the dynamic aspects will cover the remaining use cases, and groups who wonder whether established use patterns will have to fall back on loaders for so many cases that the static aspects might be defeated. Or how the static aspects are going to be integrated in a dynamic web software delivery context.

Just as with classes, even the best ideas need to be confronted with practice (of which home-brew modules in JS have had a lot).

Claus

PS. module M from URL; import item from M;

# John J Barton (12 years ago)

On Thu, Mar 22, 2012 at 3:06 PM, Sam Tobin-Hochstadt <samth at ccs.neu.edu>

wrote:

On Thu, Mar 22, 2012 at 5:11 PM, John J Barton <johnjbarton at johnjbarton.com> wrote:

The module solution posted seems to have a top-notch solution for the static case but the dynamic case is buried in the loader.

I don't really understand what you mean by "buried in the loader".

Here's the dynamic case:

let $; system.load("code.jquery.com/jquery-1.7.2.js", function(m) { $ =

m; });

Is there something more that you're looking for here?

I mean that no such line or similar example appears in either harmony:modules or harmony:module_loaders

The examples on the former page are like: import "bar.js" as Bar; import y from Bar; and all of the discussion is about this static form.

So I gather that the dynamic case would look like:

system.load(jqueryPath, function(m) { system.load(shimLibPath, function(n) { system.load(corpLibPath, function(o) { system.load(myLibPath, function (p) { import * from m; import * from n; import logHelper from o; import y from p; ... }); }); }); });

Is this correct? To me this form looks like a poor cousin. Maybe its the best we can do?

If I go back to my previous question, can we understand what should happen here?

if (version === 1) import y from 'lib1.js'; else import y from 'lib2.js';

To me the answer should be that the lib1.js and lib2.js are fetched (because I did not specify async). But they should not be executed (or compiled IMO) until the statement executes. That does not seem to be what Brendan thinks.

jjb

# Brendan Eich (12 years ago)

John J Barton wrote:

If I go back to my previous question, can we understand what should happen here?

if (version === 1) import y from 'lib1.js'; else import y from 'lib2.js';

Again, no.

  1. We are not breaking run-to-completion by nesting (conditionally on control flow) blocking event loops.

  2. Also we are not losing static binding by having the names injected by static syntactic forms depend on control flow dynamics.

To me the answer should be that the lib1.js and lib2.js are fetched (because I did not specify async). But they should not be executed (or compiled IMO) until the statement executes. That does not seem to be what Brendan thinks.

Don't break the web.

Seriously, you want something that is not tenable for developers or browser implementors. Both 1 and 2 matter. You might argue 2 is not important but 1 remains. TC39 wants dynamic things to be dynamic (modules reflect when passed to load callbacks as objects with properties), and static things to bind statically.

# John J Barton (12 years ago)

On Thu, Mar 22, 2012 at 4:54 PM, Brendan Eich <brendan at mozilla.org> wrote:

John J Barton wrote:

If I go back to my previous question, can we understand what should happen here?

if (version === 1)  import y from 'lib1.js'; else  import y from 'lib2.js';

Again, no.

No, we can't understand this case? No, this is a compile error?

  1. We are not breaking run-to-completion by nesting (conditionally on control flow) blocking event loops.

Where is the blocking event loop in the example above? I thought you said the modules are pre-fetched.

  1. Also we are not losing static binding by having the names injected by static syntactic forms depend on control flow dynamics.

Well the example has control flow dynamics and names, so what happens?

To me the answer should be that the lib1.js and lib2.js are fetched (because I did not specify async). But they should not be executed (or compiled IMO) until the statement executes. That does not seem to be what Brendan thinks.

Don't break the web.

Good advice to be sure ;-)

Seriously, you want something that is not tenable for developers or browser implementors. Both 1 and 2 matter. You might argue 2 is not important but 1 remains. TC39 wants dynamic things to be dynamic (modules reflect when passed to load callbacks as objects with properties), and static things to bind statically.

I'm not arguing for anything other than clarification. Hopefully someone will just tell me that import statements can never appear in if statements.

jjb

# Brendan Eich (12 years ago)

John J Barton wrote:

On Thu, Mar 22, 2012 at 4:54 PM, Brendan Eich<brendan at mozilla.org> wrote:

John J Barton wrote:

If I go back to my previous question, can we understand what should happen here?

if (version === 1) import y from 'lib1.js'; else import y from 'lib2.js'; Again, no.

No, we can't understand this case? No, this is a compile error?

Compile error currently -- import is legal only at top level of a module or program.

  1. We are not breaking run-to-completion by nesting (conditionally on control flow) blocking event loops.

Where is the blocking event loop in the example above? I thought you said the modules are pre-fetched.

You wrote that if-else, not me. If you think the author should face prefecthing both lib1.js and lib2.js even though only one (depending on runtime control flow) is needed, good luck selling that.

  1. Also we are not losing static binding by having the names injected by static syntactic forms depend on control flow dynamics.

Well the example has control flow dynamics and names, so what happens?

It's invalid syntax per the current proposals. If you want to prefetch both, tell me which bindings are in effect statically? Both modules' bindings? What if they conflict on a given name?

Seriously, you want something that is not tenable for developers or browser implementors. Both 1 and 2 matter. You might argue 2 is not important but 1 remains. TC39 wants dynamic things to be dynamic (modules reflect when passed to load callbacks as objects with properties), and static things to bind statically.

I'm not arguing for anything other than clarification. Hopefully someone will just tell me that import statements can never appear in if statements.

That's what the current grammar says:

harmony:modules#syntax

# Claus Reinke (12 years ago)

So I gather that the dynamic case would look like:

system.load(jqueryPath, function(m) { system.load(shimLibPath, function(n) { system.load(corpLibPath, function(o) { system.load(myLibPath, function (p) { import * from m; import * from n; import logHelper from o; import y from p; ... }); }); }); });

Is this correct? To me this form looks like a poor cousin. Maybe its the best we can do?

This isn't part of any official proposal, and hasn't received any interest when I suggested it earlier, but I'd still like to see some sugar for then- ables (like promises). Roughly, the let-from form

{ let p <- t; code }     would become    t.then( {| p | code } )

So, if module loaders would return promises, and if let-from was supported, your example could be written as

let m <- system.load(jqueryPath) let n <- system.load(shimLibPath) let o <- system.load(corpLibPath) let p <- system.load(myLibPath) import * from m; import * from n; import logHelper from o; import y from p; ...

The details of let-from remain to be worked out, especially which function form to desugar to, but experience from other languages (do-notation, computation expressions) suggest that it would be very worthwhile (not just for async callbacks), if done right.

Claus

# John J Barton (12 years ago)

On Thu, Mar 22, 2012 at 10:32 AM, Brendan Eich <brendan at mozilla.org> wrote:

John J Barton wrote:

I guess you mean: a special form evaluated before the outer function runs? Surely this form is not off-line.

No, before anything in the containing Program (up to the <script> container) runs. Modules are prefetched, not loaded by a nested event loop that violates run-to-completion (or unthinkable: starves the same-page-context UI to preserve r-t-c by queueing events).

I replied to this once already, learning a bit more about async load and learning that import is only valid at the top level. However, I am still confused on the runtime model for import.

Brendan, your argument above makes it sound like import x from "Bar.js" does not block and thus would not starve the UI rendering. How can that be?

Here is my model for what has to happen:

The browser parses the html document and encounters the <script> tag; UI

progress stops. It passes the tag contents to the JS engine which encounters the import statement. Bar.js is fetched; UI progress is blocked. Next we parse Bar.js for imports and pre-fetch the modules and recurse; UI progress is blocked. Once we reach the end of the DAG we compile the first module and execute its outer function. Then the UI progress continues.

The browser's JS engine has no way to know which dependencies to load without parsing the dependents. So it has to load all of the dependencies before continuing. The load behavior will be just like <script> tags today.

To avoid starving the UI thread, developers must load the first module within each subsystem with system.load() calls, not imports. Based on our experience with require.js, we will have to either change the specification of the "load" event or develop a set of ad hoc practices to deal with the race between the asynchronous script loading and the UI load. Any JS intended to run on 'load' must block the UI.

If import semantics allowed control-flow to select modules, then the total time the UI would be blocked would be not more and could be less than the current definition. This is simply because the control-flow selection could load less code.

If the model above is correct, I don't see how UI starvation/run-to-completion has any role in selecting the import semantics, other than favoring control-flow selection.

On the other hand, now that it is clear that developers will need to carefully apply system.load() anyway, that tool will deal with control-flow selection of modules. Limiting 'import' to top level simplifies and helps organize reading the code. It also makes developers who come from static language backgrounds more comfortable since variables will be er less variable ;-).

Again if this model is correct, then I think ES should not specify module load timing. That is, the spec should not insist that all modules are fetched and analyzed before the first one is compiled and run. The control-flow-free import semantics allows pre-fetch but it also allows loading to be interleaved with compile and execution. That would be very desirable for performance. Perhaps there are other reasons to force pre-fetch?

Is the model above not correct?

jjb

# Brendan Eich (12 years ago)

John J Barton wrote:

Brendan, your argument above makes it sound like import x from "Bar.js" does not block and thus would not starve the UI rendering. How can that be?

While parsing (which can be done very early, as TCP segments come in, with some assembly across boundaries required), modules are prefetched.

This is before anything in the containing <script> element runs.

Browsers already do speculative script pre-fetching (parse ahead in HTML even if blocked on a loading element, start loading further scripts, bail if any have effects that would be out of order).

# Claus Reinke (12 years ago)
  1. Also we are not losing static binding by having the names injected by static syntactic forms depend on control flow dynamics.

When statically checkable constraints meet dynamic load/eval, it isn't necessary to have a strict static/dynamic split. It would be possible to adopt a multi-staged design, where there are multipe dynamic phases, each with its own preceding static phase: no code runs without static checks, no static properties are affected by dynamic code in the same stage.

I am trying to understand whether ES6 aims for such a multi-staged design, or whether entering a dynamic phase forever precludes the use of static constructs from the modules spec.

Part of the spec suggest multiple stages, eg. harmony:modules

Reflective evaluation, via eval or the module loading API 
starts a new compilation and linking phase for the dynamically 
evaluated code.

Other parts suggest a single stage, eg harmony:modules limits ImportDeclaration to Program and ModuleBody top-levels, ruling it out in loader callbacks. Right?

Similarly, reflecting Modules as Objects brings us from static into dynamic but -presumably- import statements work with reflected Modules. Or don't they?

Claus

# Brendan Eich (12 years ago)

Claus Reinke wrote:

When statically checkable constraints meet dynamic load/eval, it isn't necessary to have a strict static/dynamic split. It would be possible to adopt a multi-staged design, where there are multipe dynamic phases, each with its own preceding static phase: no code runs without static checks, no static properties are affected by dynamic code in the same stage.

Yes, this was all detailed by Dave as part of 1JS.

Also it's inherent in the embedding of JS in HTML. Each of several successive <script> elements is a separate staged evaluation (i.e.,

compilation) unit.