Module Comments

# Kevin Smith (11 years ago)

I've had a look at the changes to the modules wiki page [1]. I'm liking the syntax changes quite a lot actually, but I have a couple of comments:

  1. "export" ExportSpecifierSet ("," ExportSpecifierSet)* ";"

This rule seems too permissive. It allows strange combinations like:

export x, { y: a.b.c, z }, { j, k }, * from "bloop";

I think it would be better to flatten it out a little bit:

ExportDeclaration: "export" "{" ExportSpecifier ("," ExportSpecifier)*

"}" ";" ExportDeclaration: "export" Identifier ("," Identifier)* ";" ExportDeclaration: "export" "*" "from" StringLiteral ";"

  1. Do we need export *;?

I don't see the point of exporting every declaration in the current module scope. If the programmer wants to export a bunch of stuff, the other forms make that sufficiently easy. Exporting everything encourages bad (de-modular) design.

  1. I'm just OK with "as". Note that it inverts the position of the string and the binding:

    import { x } from "goo"; import "ga" as ga;

Which makes it harder to read when you have a bunch of imports mixed together. It's harder for the eyes to scan back and forth.

  1. Why was this form eliminated?

    import x from "goo"; // Note lack of curlies!

In an object-oriented application, we're often going to be importing a single thing (the class) from external modules. So we may see long lists like this:

import { ClassA } from "ClassA.js";
import { ClassB } from "ClassB.js";
....
import { ClassZ } from "ClassZ.js";

Removing the curlies for this simple case would seem like a win.

  1. Dynamic exports via export = ? could make interop with existing module systems easier. But how does that work?

  2. Adding ".js" as the default resolution strategy:

I don't think this is tenable. First, there's the practical issue of what happens when someone actually wants to load a resource without a ".js" extension?

Second, there's the philosophical problem of essentially setting up an alternate URL scheme. That's fine for libraries like AMD loaders or YUI. But EcmaScript is a cornerstone of the internet. External resource resolution can't conflict with HTML/CSS/etc.

  • Kevin

[1] harmony:modules

# David Herman (11 years ago)

On Dec 5, 2012, at 7:16 PM, Kevin Smith <khs4473 at gmail.com> wrote:

  1. "export" ExportSpecifierSet ("," ExportSpecifierSet)* ";"

This rule seems too permissive. It allows strange combinations like:

export x, { y: a.b.c, z }, { j, k }, * from "bloop";

I think it would be better to flatten it out a little bit:

ExportDeclaration: "export" "{" ExportSpecifier ("," ExportSpecifier)* "}" ";"
ExportDeclaration: "export" Identifier ("," Identifier)* ";"
ExportDeclaration: "export" "*" "from" StringLiteral ";"

Reasonable point, I'll think about that.

  1. Do we need export *;?

I don't see the point of exporting every declaration in the current module scope. If the programmer wants to export a bunch of stuff, the other forms make that sufficiently easy. Exporting everything encourages bad (de-modular) design.

Again, reasonable point.

  1. I'm just OK with "as". Note that it inverts the position of the string and the binding:

    import { x } from "goo"; import "ga" as ga;

Which makes it harder to read when you have a bunch of imports mixed together. It's harder for the eyes to scan back and forth.

Yeah, that's the known downside to this syntax. We've been around this block so many times, and it's just one of those things that will end up with bikeshed colored not in paint but in blood. I'm open to alternative suggestions but not to endless discussion (it goes nowhere).

The alternative of

import ga = "ga"

has the problem of looking like it's saying that ga is a string.

The alternatives of

import ga = module("ga")

or

import ga = (module "ga")

have the problem of making it look like the RHS is an expression.

Feel free to suggest alternatives, but forgive me if I'm not willing to respond to every opinion on this one. :}

  1. Why was this form eliminated?

    import x from "goo"; // Note lack of curlies!

In an object-oriented application, we're often going to be importing a single thing (the class) from external modules. So we may see long lists like this:

import { ClassA } from "ClassA.js";
import { ClassB } from "ClassB.js";
....
import { ClassZ } from "ClassZ.js";

Removing the curlies for this simple case would seem like a win.

Another fair point. I think it might've just been a refactoring oversight.

  1. Dynamic exports via export = ? could make interop with existing module systems easier. But how does that work?

Basic semantics:

  • The module has one single anonymous export.
  • Clients can only get access to the export with the "as" form; there's no way to access it via a named export.
  • The value of the export is undefined until the RHS is evaluated.
  1. Adding ".js" as the default resolution strategy:

I don't think this is tenable. First, there's the practical issue of what happens when someone actually wants to load a resource without a ".js" extension?

They change the resolution hook.

Second, there's the philosophical problem of essentially setting up an alternate URL scheme. That's fine for libraries like AMD loaders or YUI. But EcmaScript is a cornerstone of the internet. External resource resolution can't conflict with HTML/CSS/etc.

"I'll wait till it's fleshed out more" didn't last long, eh? ;-) Anyway, I don't really understand this argument but do you think there's much value to a philosophical debate on es-discuss?

# Herby Vojčík (11 years ago)

David Herman wrote:

On Dec 5, 2012, at 7:16 PM, Kevin Smith<khs4473 at gmail.com> wrote:

  1. I'm just OK with "as". Note that it inverts the position of the string and the binding:

    import { x } from "goo"; import "ga" as ga;

Which makes it harder to read when you have a bunch of imports mixed together. It's harder for the eyes to scan back and forth.

Yeah, that's the known downside to this syntax. We've been around this block so many times, and it's just one of those things that will end up with bikeshed colored not in paint but in blood. I'm open to alternative suggestions but not to endless discussion (it goes nowhere).

The alternative of

 import ga = "ga"

has the problem of looking like it's saying that ga is a string.

The alternatives of

 import ga = module("ga")

or

 import ga = (module "ga")

have the problem of making it look like the RHS is an expression.

Feel free to suggest alternatives, but forgive me if I'm not willing to respond to every opinion on this one. :}

import x from "goo"; import ga for "ga";

import x from "goo"; import ga "ga";

# David Herman (11 years ago)

On Dec 5, 2012, at 10:41 PM, Herby Vojčík <herby at mailbox.sk> wrote:

import x from "goo";

Already taken. You can't use one syntax to mean two things.

import ga for "ga";

That doesn't have any correspondence to its meaning in English.

import x from "goo";

That's identical to your first suggestion.

import ga "ga";

Awkward.

# Herby Vojčík (11 years ago)

David Herman wrote:

On Dec 5, 2012, at 10:41 PM, Herby Vojčík<herby at mailbox.sk> wrote:

import x from "goo";

Already taken. You can't use one syntax to mean two things.

I don't. These were included to show the two syntaxes (existing from and proposed instead-of-as grouped together; to see how they look mixed).

import ga for "ga";

That doesn't have any correspondence to its meaning in English.

It does. import ga for (the whole module) "ga". Alternative was import ga for module "ga";.

import x from "goo";

That's identical to your first suggestion.

Same as above.

# Matthew Robb (11 years ago)

I don't see why you can't treat it like this: import <local accessor> from <resource>

import ga from "ga";

If you want to import specific exports then use curlies or dot notation

import ga.foo from "ga"; import { foo } from "ga";

# David Herman (11 years ago)

On Dec 5, 2012, at 11:05 PM, Herby Vojčík <herby at mailbox.sk> wrote:

import x from "goo";

Already taken. You can't use one syntax to mean two things.

I don't. These were included to show the two syntaxes (existing from and proposed instead-of-as grouped together; to see how they look mixed).

Ah, my misunderstanding, thanks for clarifying.

import ga for "ga";

That doesn't have any correspondence to its meaning in English.

It does. import ga for (the whole module) "ga". Alternative was import ga for module "ga";.

It doesn't work, at least not with the verb "import." What you're trying to say is "import the module 'ga' with the name ga as its local binding." You're not importing the binding itself.

# David Herman (11 years ago)

On Dec 5, 2012, at 11:09 PM, Matthew Robb <matthewwrobb at gmail.com> wrote:

I don't see why you can't treat it like this: import <local accessor> from <resource>

import ga from "ga";

We really don't want from to be dual-purposed to sometimes mean the whole module itself, and sometimes extracting exports from the module. The syntax should make it absolutely clear which is which.

If you want to import specific exports then use curlies or dot notation

import ga.foo from "ga";

That's really weird. You essentially are allowing any identifier whatsoever to the left of the dot.

# Brendan Eich (11 years ago)

David Herman wrote:

On Dec 5, 2012, at 7:16 PM, Kevin Smith<khs4473 at gmail.com> wrote:

  1. "export" ExportSpecifierSet ("," ExportSpecifierSet)* ";"

This rule seems too permissive. It allows strange combinations like:

 export x, { y: a.b.c, z }, { j, k }, * from "bloop";

I think it would be better to flatten it out a little bit:

 ExportDeclaration: "export" "{" ExportSpecifier ("," ExportSpecifier)* "}" ";"
 ExportDeclaration: "export" Identifier ("," Identifier)* ";"
 ExportDeclaration: "export" "*" "from" StringLiteral ";"

Reasonable point, I'll think about that.

We do not so limit var, let, or const -- why should we limit export? Ok, it's a bit different (but then import is not, right?).

Style enforcement should not be wired into the grammar. If I want to write

export {odd, even}, {nat: naturals};

why not?

Skipping 'as' reversal, that is a tough one! The export = ... special form does not suggest a better syntax, alas.

  1. Why was this form eliminated?

    import x from "goo"; // Note lack of curlies!

In an object-oriented application, we're often going to be importing a single thing (the class) from external modules. So we may see long lists like this:

 import { ClassA } from "ClassA.js";
 import { ClassB } from "ClassB.js";
 ....
 import { ClassZ } from "ClassZ.js";

Removing the curlies for this simple case would seem like a win.

Another fair point. I think it might've just been a refactoring oversight.

Cool, definitely want the plain identifier form, it's part of the binding (and destructuring) pattern language.

# David Herman (11 years ago)

On Dec 5, 2012, at 11:52 PM, Brendan Eich <brendan at mozilla.org> wrote:

ExportDeclaration: "export" "{" ExportSpecifier ("," ExportSpecifier)* "}" ";"
ExportDeclaration: "export" Identifier ("," Identifier)* ";"
ExportDeclaration: "export" "*" "from" StringLiteral ";"

Reasonable point, I'll think about that.

We do not so limit var, let, or const -- why should we limit export? Ok, it's a bit different (but then import is not, right?).

Style enforcement should not be wired into the grammar. If I want to write

export {odd, even}, {nat: naturals};

why not?

Fairly agnostic about this, given that you can express most combinations within the { ... } form. But I'll think about it.

Removing the curlies for this simple case would seem like a win.

Another fair point. I think it might've just been a refactoring oversight.

Cool, definitely want the plain identifier form, it's part of the binding (and destructuring) pattern language.

Well, the thing is it isn't consistent with the destructuring meaning: dropping the curlies here means extracting a single export (aka property), which is not what it means in destructuring assignment/binding anywhere else.

But that said, the convenience may well still trump the inconsistency.

# Jussi Kalliokoski (11 years ago)

On Thu, Dec 6, 2012 at 8:25 AM, David Herman <dherman at mozilla.com> wrote:

On Dec 5, 2012, at 7:16 PM, Kevin Smith <khs4473 at gmail.com> wrote:

  1. "export" ExportSpecifierSet ("," ExportSpecifierSet)* ";"

This rule seems too permissive. It allows strange combinations like:

export x, { y: a.b.c, z }, { j, k }, * from "bloop";

I think it would be better to flatten it out a little bit:

ExportDeclaration: "export" "{" ExportSpecifier (","

ExportSpecifier)* "}" ";"

ExportDeclaration: "export" Identifier ("," Identifier)* ";"
ExportDeclaration: "export" "*" "from" StringLiteral ";"

Reasonable point, I'll think about that.

  1. Do we need export *;?

I don't see the point of exporting every declaration in the current module scope. If the programmer wants to export a bunch of stuff, the other forms make that sufficiently easy. Exporting everything encourages bad (de-modular) design.

Again, reasonable point.

  1. I'm just OK with "as". Note that it inverts the position of the string and the binding:

    import { x } from "goo"; import "ga" as ga;

Which makes it harder to read when you have a bunch of imports mixed together. It's harder for the eyes to scan back and forth.

Yeah, that's the known downside to this syntax. We've been around this block so many times, and it's just one of those things that will end up with bikeshed colored not in paint but in blood. I'm open to alternative suggestions but not to endless discussion (it goes nowhere).

The alternative of

import ga = "ga"

has the problem of looking like it's saying that ga is a string.

The alternatives of

import ga = module("ga")

or

import ga = (module "ga")

have the problem of making it look like the RHS is an expression.

Feel free to suggest alternatives, but forgive me if I'm not willing to respond to every opinion on this one. :}

Replace the current from form with of and use from for the single export:

import go from "ga"; // prev: import "ga" as go import {a: b, b: a} of "ta"; // prev: import {a: b, b:a} from "ta"

It would even make the common case shorter. :P

Or keep the current from form and:

import go off "ga"; // prev: import "ga" as go

Or maybe in, but it sounds really weird in English as import has an implicit in.

# Claus Reinke (11 years ago)

Well, the thing is it isn't consistent with the destructuring meaning: dropping the curlies here means extracting a single export (aka property), which is not what it means in destructuring assignment/binding anywhere else.

But that said, the convenience may well still trump the inconsistency.

I think I'd prefer consistency here, as it also allows to get rid of

import "foo" as foo;

and replace it with

import foo from "foo";

which keeps the order of

import {x,y} from foo

and it is all just module-level destructuring (fewer new concepts).

Claus

# Sam Tobin-Hochstadt (11 years ago)

On Thu, Dec 6, 2012 at 1:25 AM, David Herman <dherman at mozilla.com> wrote:

On Dec 5, 2012, at 7:16 PM, Kevin Smith <khs4473 at gmail.com> wrote:

  1. Do we need export *;?

I don't see the point of exporting every declaration in the current module scope. If the programmer wants to export a bunch of stuff, the other forms make that sufficiently easy. Exporting everything encourages bad (de-modular) design.

Again, reasonable point.

I strongly disagree. There are two important use cases for export * -- exporting everything you've defined, for little convenience modules or other quick tasks, and for wrapper modules where you want to export everything you import from somewhere else. Not having it means that you have to write everything explicitly, but more than that means that you end up with annoying busy work when you forget, and have to go figure out where the missing import or export is. I don't see any benefit to leaving it out (unlike import *, which was genuinely complex), and I don't think we need to tell programmers what good design is.

# Kevin Smith (11 years ago)

Summaries with comments:

  1. "export" ExportSpecifierSet ("," ExportSpecifierSet)* ";"

Brendan pointed out that other binding forms allow lists (including the import form), so why not this one? I actually didn't realize that lists are allowed with import:

ImportDeclaration ::= "import" ImportClause ("," ImportClause)* ";"

Fair enough.

  1. Do we need export *;?

Sam pointed out two use cases:

  1. Conveniently exporting everything from "little convenience modules".

I'm not really convinced by this. It's just so easy to export things by adding the export keyword.

  1. Re-exporting a collection of imports from other sources.

This is better, at least in the case where you want to import a well-defined set of bindings and then re-export those same bindings later. Without export *;, you'd have to keep those binding lists in sync. Sounds reasonable to me.

  1. I'm just OK with "as". Note that it inverts the position of the string

and the binding:

Claus and Matthew suggested using the "no-curlies" form instead:

import ga from "ga";

Dave responded by pointing out that we don't want "from" to have overloaded semantics. I actually think this form has potential, because it further aligns import with the other binding forms:

var a = x;
var { b } = y;

// Symmetry!

import a from "x";
import { b } from "y";
  1. Dynamic exports via export = ? could make interop with existing module

systems easier. But how does that work?

Dave gave an outline. I'm liking this. What are the downsides, if any?

  1. Adding ".js" as the default resolution strategy:

Dave responded that a custom resolution hook would be required for resources that don't end in ".js". While that certainly works for current loader libraries, can we really mandate a "js" extension for all module URLs, forever? Because this default will effectively mandate that. Imagine if such a mandate came from CSS. URLs are a backbone concept of the internet and we shouldn't go fiddling with it.

Dave also responded:

"I'll wait till it's fleshed out more" didn't last long, eh? ;-) Anyway,

I don't really

understand this argument but do you think there's much value to a

philosophical

debate on es-discuss?

Well, I practice Olympian levels of patience with my four small children day in and day out, so you'll have to forgive me if I've got none left over for you, good sir. ; )

# Erik Arvidsson (11 years ago)

On Thu, Dec 6, 2012 at 9:42 AM, Kevin Smith <khs4473 at gmail.com> wrote:

Dave responded by pointing out that we don't want "from" to have overloaded semantics. I actually think this form has potential, because it further aligns import with the other binding forms:

var a = x;
var { b } = y;

// Symmetry!

import a from "x";
import { b } from "y";

+1

This also matches what Yehuda and Domenic proposed a few weeks ago.

# Axel Rauschmayer (11 years ago)

+1

Compared to Node.js, I love ES6 exports, but Node.js module imports are easier to understand.

Claus’ suggestion would make import similar to let and people could basically think of modules as objects.

# Domenic Denicola (11 years ago)

For the record, here's the idea Yehuda and I worked out:

gist.github.com/1ab3f0daa7b37859ce43

I would really appreciate if people read it (it's easy reading, I promise!) and incorporated some of our concerns and ideas into their thinking on module syntax.

In general, it tries to eliminate the ExportSpecifierSet and ImportSpecifierSet microsyntaxes in favor of standard object literal/destructuring forms, respectively. It also made export and import syntax symmetric.

It apparently failed to get much traction among committee members, mainly because of objections to our reformation of the export-side syntax. (Second-hand info.) E.g. the current export function foo() { } was preferred to the proposed export { foo() { } }, and---less aesthetically---our special behavior for ObjectLiteral expressions over other expressions was a bit weird.

I still think the import-side reformation is important and stand by everything in the proposal in that regard. As mentioned in other messages in this thread, the current form is confusingly dual-natured. And I maintain that the incomplete semi-destructuring microsyntax of ImportSpecifierSet adds a lot of cognitive burden.

If we can start by fixing the import side, I have some other ideas on how to make the export side better that hopefully will be more appealing to the committee. But I don't want to try proposing those yet until we can get the buy-in on fixing import.

# Andreas Rossberg (11 years ago)

On 6 December 2012 15:42, Kevin Smith <khs4473 at gmail.com> wrote:

  1. Dynamic exports via export = ? could make interop with existing module systems easier. But how does that work?

Dave gave an outline. I'm liking this. What are the downsides, if any?

The downside is that it introduces a severe anomaly into the module semantics (a module which actually has no instance). I could live with this feature if we were to find a way to explain it in terms of simple syntactic sugar on both the import and export side, but screwing and complicating the semantics for minor syntactic convenience is not something I am particularly fond of.

# Yehuda Katz (11 years ago)

One plausible variant to our proposal that might be easier to swallow (as it doesn't have the special-cased ObjectLiteral problem) is:

  • Replace export ExportSpecifierSet with export ObjectLiteral for static exports
  • Allow export = foo (or something like it) for single exports
  • Unify the import syntax to support the full destructuring syntax and to work on both static and single exports

This retains the advantages of reducing micro-syntax and reforming import without forcing an awkward special-casing of export ObjectLiteral

# Andreas Rossberg (11 years ago)

On 6 December 2012 16:44, Domenic Denicola <domenic at domenicdenicola.com> wrote:

For the record, here's the idea Yehuda and I worked out:

gist.github.com/1ab3f0daa7b37859ce43

I would really appreciate if people read it (it's easy reading, I promise!) and incorporated some of our concerns and ideas into their thinking on module syntax.

I strongly agree with having the

import x from ... import {x, y} from ...

symmetry and consistent binding on the left. However, the more radical parts of your proposal (allowing arbitrary export expressions, and arbitrary import patterns) do not work.

The problem is that imports are not normal variable assignments. They do not copy values, like normal destructuring, they are aliasing bindings! If you were to allow arbitrary expressions and patterns, then this would imply aliasing of arbitrary object properties. Not only is this a completely new feature, it also is rather questionable -- the aliased location might disappear, because objects are mutable.

Consider:

module A { let o = { x: [1, 2], f() { o.x = 666 } } export o }

import {x: [a, b], f} from A a = 3 // is this supposed to modify the array? f() print(a) // x is no longer an array, a doesn't even exist

In other words, what you are proposing has no longer anything to do with static scoping.

You could arguably make this saner by interpreting nested patterns in an import as copying, not aliasing, but I think mixing meanings like that would be rather confusing and surprising.

You could also consider imports always meaning copying, but then you exporting a variable will no longer be useful.

# Kevin Smith (11 years ago)

The downside is that it introduces a severe anomaly into the module semantics (a module which actually has no instance). I could live with this feature if we were to find a way to explain it in terms of simple syntactic sugar on both the import and export side, but screwing and complicating the semantics for minor syntactic convenience is not something I am particularly fond of.

What if this:

export = "boo";

Actually creates a static export with some exotic name, say DEFAULT (for the sake of argument) and initializes it to the value "boo".

And this form:

import boo from "boo.js";

Creates a binding to DEFAULT in "boo.js", if it exists, or to the module instance of "boo.js" otherwise.

Would that work as a desugaring?

# Andreas Rossberg (11 years ago)

On 6 December 2012 17:33, Kevin Smith <khs4473 at gmail.com> wrote:

The downside is that it introduces a severe anomaly into the module semantics (a module which actually has no instance). I could live with this feature if we were to find a way to explain it in terms of simple syntactic sugar on both the import and export side, but screwing and complicating the semantics for minor syntactic convenience is not something I am particularly fond of.

What if this:

export = "boo";

Actually creates a static export with some exotic name, say DEFAULT (for the sake of argument) and initializes it to the value "boo".

And this form:

import boo from "boo.js";

Creates a binding to DEFAULT in "boo.js", if it exists, or to the module instance of "boo.js" otherwise.

Would that work as a desugaring?

I suggested something along these lines at some point in the past, but there were some concerns with it that, unfortunately, I do not remember. Maybe it can be resolved.

Note, however, that you still assume some hack in the semantics with the "if it exists" part. To avoid that, you need to divorce the import syntax from the naming-an-external-module syntax -- which I'd actually prefer anyway, and which was the case in the previous version of the proposal.

# Matthew Robb (11 years ago)

What about trying it the other way, flip everything.

import "foo" as bar; import "foo" as { baz }

# Andreas Rossberg (11 years ago)

On 6 December 2012 17:46, Matthew Robb <matthewwrobb at gmail.com> wrote:

What about trying it the other way, flip everything.

import "foo" as bar; import "foo" as { baz }

Hm, I don't understand. What would that solve?

# Matthew Robb (11 years ago)

Well the argument being that as it stands it's inconsistent having the identifier on the RHS for import from and LHS in import as:

import baz from "foo"; import "foo" as foo;

I'm just throwing out simple options for solving that inconsistency.

Possibly even thinking about them as declarations makes the simplest sense anyway:

var foo = import "foo"; var { baz } = import "foo";

# Kevin Smith (11 years ago)

Note, however, that you still assume some hack in the semantics with the "if it exists" part. To avoid that, you need to divorce the import syntax from the naming-an-external-module syntax -- which I'd actually prefer anyway, and which was the case in the previous version of the proposal.

Could we eliminate the hack on the export side instead?

Every module instance has a $DEFAULT export binding. Normally, it is set to the module instance itself. export = ? overrides the value of that binding. import x from "y" binds $DEFAULT in "y" to x. Maybe?

# Brandon Benvie (11 years ago)

Every module instance has a $DEFAULT export binding. Normally, it is

set to the module instance itself. export = ? overrides the value of that binding. >> import x from "y" binds $DEFAULT in "y" to x. Maybe?

That would be how Node.js does. For those unfamiliar with how it works, it's basically like this

var mod = { exports: {} }; (function(module, exports){ ... })(mod, mod.exports);

So if you assign things to the default exports like exports.firstExport = value; exports.secondExport = value;

You're assigning to the default provided export object. But since you also have access to the container (module) you can opt to fully overwrite it module.exports = singleExportedThing;

If you added things to exports and then overwrote module.exports with a new value, those initial exports will not be visible as exports because the original export object is overwritten. exports.willBeOverwritten = true; module.exports = singleExportedThing;

So the single export route is opt-in, but it's opted into in the large majority of cases.

# Brendan Eich (11 years ago)

David Herman wrote:

Cool, definitely want the plain identifier form, it's part of the binding (and destructuring) pattern language.

Well, the thing is it isn't consistent with the destructuring meaning: dropping the curlies here means extracting a single export (aka property), which is not what it means in destructuring assignment/binding anywhere else.

You are so right. Therefore I think Yehuda et al. (as Andreas affirmed) are spot-on in advocating

import foo from "foo"; // import the singleton export = thing from "foo" import {bar} from "foo"; // import the bar export from "foo"

and we don't have a RTL problem.

# Andreas Rossberg (11 years ago)

On 6 December 2012 17:54, Kevin Smith <khs4473 at gmail.com> wrote:

Note, however, that you still assume some hack in the semantics with the "if it exists" part. To avoid that, you need to divorce the import syntax from the naming-an-external-module syntax -- which I'd actually prefer anyway, and which was the case in the previous version of the proposal.

Could we eliminate the hack on the export side instead?

Every module instance has a $DEFAULT export binding. Normally, it is set to the module instance itself. export = ? overrides the value of that binding. import x from "y" binds $DEFAULT in "y" to x. Maybe?

Well, in my book, that doesn't count as eliminating the hack, but rather broadening it to all sides. Moreover, it still prevents you from getting a handle on the module itself. In fact, I believe this is pretty much equivalent to what's currently in the proposal.

For the record, what I have in mind is similar to your previous suggestion, namely treating

export = exp

as special syntax for the pseudo declaration

export let export = exp

(where the second 'export' is meant to act as an identifier/property name). And e.g.

import x from "url"

as

import {export: x} from "url"

For module naming, we'd need to have a different syntax. In earlier versions of the proposal that was

module x at "url"

which would still be usable even for modules using special exports. Note also that using the special export would not be mutually exclusive with having other exports, so in that sense, it is like your $DEFAULT, but far less magic.

# Russell Leggett (11 years ago)

On Thu, Dec 6, 2012 at 1:46 PM, Brendan Eich <brendan at mozilla.org> wrote:

David Herman wrote:

Cool, definitely want the plain identifier form, it's part of the binding (and destructuring) pattern language.

Well, the thing is it isn't consistent with the destructuring meaning: dropping the curlies here means extracting a single export (aka property), which is not what it means in destructuring assignment/binding anywhere else.

You are so right. Therefore I think Yehuda et al. (as Andreas affirmed) are spot-on in advocating

import foo from "foo"; // import the singleton export = thing from "foo" import {bar} from "foo"; // import the bar export from "foo"

and we don't have a RTL problem.

+1

For some reason I thought this is actually how it was working at some point, but I haven't been following as closely as I should.

# Nathan Wall (11 years ago)

Feel free to suggest alternatives, but forgive me if I'm not willing to respond to every opinion on this one. :}

How about:

import "ga" as ga;

to import the whole module, and

import "ga" as { foo, bar };

to import just parts of the module. I feel like this makes a lot of sense, and is similar to the destructuring pattern.

let ga = new Ga();

vs

let { foo, bar } = new Ga();

Nathan

# Kevin Smith (11 years ago)

So if you didn't set the anonymous binding in some module "x.js", what would this do:

import x from "x.js";

Would x be bound to the module instance or would we get a binding error?

For module naming, we'd need to have a different syntax. In earlier

versions of the proposal that was

module x at "url"

Or:

import module x from "url";

Has a nice ring.

# Domenic Denicola (11 years ago)

-----Original Message----- From: Andreas Rossberg [mailto:rossberg at google.com] Sent: Thursday, December 6, 2012 11:31

On 6 December 2012 16:44, Domenic Denicola <domenic at domenicdenicola.com> wrote:

For the record, here's the idea Yehuda and I worked out:

gist.github.com/1ab3f0daa7b37859ce43

I would really appreciate if people read it (it's easy reading, I promise!) and incorporated some of our concerns and ideas into their thinking on module syntax.

However, the more radical parts of your proposal (allowing arbitrary export expressions, and arbitrary import patterns) do not work.

The problem is that imports are not normal variable assignments. They do not copy values, like normal destructuring, they are aliasing bindings! If you were to allow arbitrary expressions and patterns, then this would imply aliasing of arbitrary object properties. Not only is this a completely new feature, it also is rather questionable -- the aliased location might disappear, because objects are mutable.

Thanks for the feedback Andreas; this is really helpful. It took me a while to figure out what you meant by this, but I think I understand now. However, I think that since the bindings are const bindings, the difference between copying and aliasing is unobservable?is that right? Thus the mental model could be copying in all cases, both top-level and deeper.

I think the symmetry I am looking for is that

import { x: [a, b], f } from "foo";

should work the same as

import foo from "foo";
const { x: [a, b], f } = foo;

And I realize the linked gist does not introduce const bindings, but instead let bindings; that's easy enough to fix if it's the right path forward.

You could also consider imports always meaning copying, but then you exporting a variable will no longer be useful.

This is the part that made me question whether I understand what you're saying. What do you mean by "exporting a variable" and "useful"?

# Herby Vojčík (11 years ago)

Domenic Denicola wrote:

-----Original Message----- From: Andreas Rossberg [mailto:rossberg at google.com] Sent: Thursday, December 6, 2012 11:31

On 6 December 2012 16:44, Domenic Denicola <domenic at domenicdenicola.com> wrote:

For the record, here's the idea Yehuda and I worked out:

gist.github.com/1ab3f0daa7b37859ce43

I would really appreciate if people read it (it's easy reading, I promise!) and incorporated some of our concerns and ideas into their thinking on module syntax. However, the more radical parts of your proposal (allowing arbitrary export expressions, and arbitrary import patterns) do not work.

The problem is that imports are not normal variable assignments. They do not copy values, like normal destructuring, they are aliasing bindings! If you were to allow arbitrary expressions and patterns, then this would imply aliasing of arbitrary object properties. Not only is this a completely new feature, it also is rather questionable -- the aliased location might disappear, because objects are mutable.

Thanks for the feedback Andreas; this is really helpful. It took me a while to figure out what you meant by this, but I think I understand now. However, I think that since the bindings are const bindings, the difference between copying and aliasing is unobservable—is that right? Thus the mental model could be copying in all cases, both top-level and deeper.

I think the symmetry I am looking for is that

import { x: [a, b], f } from "foo";

should work the same as

import foo from "foo"; const { x: [a, b], f } = foo;

No, afaicr that was not the idea, see below.

And I realize the linked gist does not introduce const bindings, but instead let bindings; that's easy enough to fix if it's the right path forward.

You could also consider imports always meaning copying, but then you exporting a variable will no longer be useful.

This is the part that made me question whether I understand what you're saying. What do you mean by "exporting a variable" and "useful"?

The part about "exporting a variable" is in fact explaining that it is not about const as you noted above (cp in unix PoV), but really copy of the binding (ln in unix PoV).

That is, when you in "foo"

export let FOO = 42;

and do

import { foo } from "foo";

in two places, then "foo = -1;" in one place means it is -1 in all other places as well.

I am right in what Andreas meant, then it leads me to the question: is it really meant to be that way? Isn't copying the value (used all over in the language) better way? If I want a shareable value, I export an object that has it as a property. Should export and import be by-reference?

# Andreas Rossberg (11 years ago)

On 9 December 2012 02:10, Kevin Smith <khs4473 at gmail.com> wrote:

So if you didn't set the anonymous binding in some module "x.js", what would this do:

import x from "x.js";

Would x be bound to the module instance or would we get a binding error?

Since it is just sugar, and supposed to be equivalent to the expansion, you (fortunately) would get an error (statically).

# Andreas Rossberg (11 years ago)

On 9 December 2012 03:51, Domenic Denicola <domenic at domenicdenicola.com> wrote:

From: Andreas Rossberg [mailto:rossberg at google.com] On 6 December 2012 16:44, Domenic Denicola <domenic at domenicdenicola.com> wrote:

For the record, here's the idea Yehuda and I worked out:

gist.github.com/1ab3f0daa7b37859ce43

I would really appreciate if people read it (it's easy reading, I promise!) and incorporated some of our concerns and ideas into their thinking on module syntax.

However, the more radical parts of your proposal (allowing arbitrary export expressions, and arbitrary import patterns) do not work.

The problem is that imports are not normal variable assignments. They do not copy values, like normal destructuring, they are aliasing bindings! If you were to allow arbitrary expressions and patterns, then this would imply aliasing of arbitrary object properties. Not only is this a completely new feature, it also is rather questionable -- the aliased location might disappear, because objects are mutable.

Thanks for the feedback Andreas; this is really helpful. It took me a while to figure out what you meant by this, but I think I understand now. However, I think that since the bindings are const bindings, the difference between copying and aliasing is unobservable—is that right?

No, because what you are aliasing isn't const. Consider:

module A { export let x = 4 export function f() { x = 5 } }

import {x, f} from A f() print(x) // 5

Moreover, it is still up in the air whether exported mutable bindings should be mutable externally or not. V8, for example, currently allows that, and although it doesn't implement 'import' yet you can access the module directly:

A.x = 6 print(A.x) // 6

That is the natural behaviour if you want to be able to use modules as a name spacing mechanism. Same in TypeScript, by the way.

You could also consider imports always meaning copying, but then you exporting a variable will no longer be useful.

This is the part that made me question whether I understand what you're saying. What do you mean by "exporting a variable" and "useful"?

I hope the example(s) clarify it. :)

# Nathan Wall (11 years ago)

The problem is that imports are not normal variable assignments. They do not copy values, like normal destructuring, they are aliasing bindings! If you were to allow arbitrary expressions and patterns, then this would imply aliasing of arbitrary object properties. Not only is this a completely new feature, it also is rather questionable -- the aliased location might disappear, because objects are mutable.

Could it be structured so that using export directly on a variable exported the alias, while using import { x: [ a, b ] } from A; was basically just sugar for import { x } from A; let [ a, b ] = x; so that a and b copied not aliased? Example: module A { export let x = { a: 1, b: 2 }; export function f() { x = { a: 3, b: 4 }; }; } ... import { x: { a, b }, f } from A; f(); print(a); // 1 print(b); // 2 ... import { x, f } from A; f(); print(x.a); // 3 print(x.b); // 4

# Nathan Wall (11 years ago)

I see my previous email did not format well in the archive. I'm retrying this. (Could someone please give me some formatting tips?) Nathan

The problem is that imports are not normal variable assignments. They do not copy values, like normal destructuring, they are aliasing bindings! If you were to allow arbitrary expressions and patterns, then this would imply aliasing of arbitrary object properties. Not only is this a completely new feature, it also is rather questionable -- the aliased location might disappear, because objects are mutable.

Could it be structured so that using export directly on a variable exported the alias, while using import { x: [ a, b ] } from A; was basically just sugar for import { x } from A; let [ a, b ] = x; so that a and b copied not aliased?

Example:

module A {
  export let x = { a: 1, b: 2 };
  export function f() { x = { a: 3, b: 4 }; };
}

...

import { x: { a, b }, f } from A;
f();
print(a); // 1
print(b); // 2

...

import { x, f } from A;
f();
print(x.a); // 3
print(x.b); // 4
# Andreas Rossberg (11 years ago)

On 9 December 2012 15:04, Nathan Wall <nathan.wall at live.com> wrote:

The problem is that imports are not normal variable assignments. They do not copy values, like normal destructuring, they are aliasing bindings! If you were to allow arbitrary expressions and patterns, then this would imply aliasing of arbitrary object properties. Not only is this a completely new feature, it also is rather questionable -- the aliased location might disappear, because objects are mutable.

Could it be structured so that using export directly on a variable exported the alias, while using import { x: [ a, b ] } from A; was basically just sugar for import { x } from A; let [ a, b ] = x; so that a and b copied not aliased?

That's what I referred to when I wrote:

You could arguably make this saner by interpreting nested patterns in an import as copying, not aliasing, but I think mixing meanings like that would be rather confusing and surprising.

So yes, you could do that, but no, I don't think it is a good idea. Your example:

import { x: { a, b }, f } from A;
f();
print(a); // 1
print(b); // 2

...

import { x, f } from A;
f();
print(x.a); // 3
print(x.b); // 4

demonstrates perfectly how it violates the principle of least surprise and can potentially lead to subtle bugs, especially when refactoring. One overarching principle of destructuring should be that all variables in one binding are treated consistently.

# Nathan Wall (11 years ago)

Yeah, that makes sense. I agree. Thanks.

Nathan

# Kevin Smith (11 years ago)

Since it is just sugar, and supposed to be equivalent to the expansion, you (fortunately) would get an error (statically).

OK, then suppose we have these two separate forms:

import x from "url"; // Bind x to the anonymous export, if defined,

otherwise error

and

import module x from "url"; // Bind x to the module instance

In the vast majority of cases the "module" keyword above can be inferred correctly at link-time based on whether or not there is an anonymous export in the target module.

If it were important for the user to disambiguate in those rare cases, and load the module instance instead of the anonymous export, then she could simply provide the optional "module" keyword.

Does that work?

# Andreas Rossberg (11 years ago)

On 10 December 2012 05:30, Kevin Smith <khs4473 at gmail.com> wrote:

OK, then suppose we have these two separate forms:

import x from "url"; // Bind x to the anonymous export, if defined,

otherwise error

and

import module x from "url"; // Bind x to the module instance

In the vast majority of cases the "module" keyword above can be inferred correctly at link-time based on whether or not there is an anonymous export in the target module.

If it were important for the user to disambiguate in those rare cases, and load the module instance instead of the anonymous export, then she could simply provide the optional "module" keyword.

Does that work?

I consider such second-guessing of user intention, which can lead one construct to mean completely different things, harmful. It makes code less readable and more brittle. And again, it's a semantic hack, making the language more complex. I just don't see why it would be worth it, especially since with the right choice of syntax, the two forms of declaration can easily be made equally concise.

What's so terrible about using different constructs for different things that you want to avoid it?

# Kevin Smith (11 years ago)

I consider such second-guessing of user intention, which can lead one construct to mean completely different things, harmful. It makes code less readable and more brittle. And again, it's a semantic hack, making the language more complex. I just don't see why it would be worth it, especially since with the right choice of syntax, the two forms of declaration can easily be made equally concise.

That's true.

What's so terrible about using different constructs for different things that you want to avoid it?

I have an intuition (which may be a holdover from CommonJS modules) that a module is a kind of "function" which "returns" either a single binding or a set of named bindings, and the dual-use syntax matches that intuition.

But I agree it's conceptually more tidy with two distinct forms.