simple modules

David Herman dherman at mozilla.com
Wed Feb 3 12:20:22 PST 2010


Hi Mark,

Thanks for the detailed feedback.

> * You list "Dynamic upgrade" in the goals and then mention it again in a TBD. I do not understand how this proposal could be stretched to accommodate dynamic upgrade. I also do not think this is a criticism. I only mention it since it is one of your goals.

Some of the pieces are there, or shadows of them anyway, insofar as you could create new contexts and dynamically instantiate modules. And if contexts can share other module instantiations it's at least conceivable that you could make it work. But I haven't thought it through much.

This isn't my #1 top priority, especially since you can achieve some of it at the program level (e.g., by dynamically deciding to call a new function that you loaded off the wire instead of your old function). But to support long-running web apps it's worth considering. And Erlang is probably the top place to look for prior art.


> * I find your import syntax too complicated and too redundant with other concept already on the proposals page. Given <http://wiki.ecmascript.org/doku.php?id=harmony:destructuring>, I would define your ImportDeclaration as
> 
>     ImportDeclaration ::= 'import' Pattern 'from' ModuleSpecifier ';'

I'm not crazy about this, but I'm not vehemently against, either. It's maybe a bit soon for syntax design. The proposal doesn't rest too strongly on it, at any rate.

The most important aspects, in my mind, are that:

- None of the forms destroy static scoping.

- It's reasonably easy to import all -- this convenience matters, even though I agree it's not great style for robust programs.

- It's possible to rename bindings to work around ambiguities.

Both your suggestion and my strawman syntax achieve all these goals, I believe.

> First, the trivial issue. The ES5 grammar specifies semicolons as literal semicolons as above and leaves it to other language to specify semicolon insertion rules. Your grammar says "(';')?", which confuses the issue. I assume this was either just informal or a typo.

It was meant to be optional semicolons but I didn't think about it carefully at all. I don't much care whether they're optional. As far as I'm concerned, if automatic insertion causes any difficulty, then we should just require 'em.

> With the above grammar, we no longer need your ImportSpecifier or ImportDeclarator productions. And we can also avoid YET ANOTHER OVERLOADING OF COLON. (Please keep in mind that type-like declarations probably will overload colon, so please let's keep it to a dull roar. Perhaps we should use '=' instead of 'from', in order to make the import production be more obviously an additional binding form.

You've retracted your = suggestion, but I'm also not attached to the colon. It was just the first token I found that didn't look insane to me.

> I do not like open import for the reason Mike Samuel stated: adding an exported symbol to Math should not break existing clients of Math. If all those clients only do closed import as above, it won't. If they do open imports from Math and one other module, then the newly exported name may conflict with another open import, breaking existing clients. Granted, they break with an early error, but still. In any case, *if* we do want open import, I think a better syntax is
> 
>    Your....                    Becomes....
> 
>    import "Math"               import {*} from Math
> 
> or perhaps
> 
>    import "Math"               import * from Math

I believe open import is crucial for simple scripts. The nice thing about second-class modules is it makes open import sane, statically scoped, and fail-fast. That is, in the upgrade-hazard you mention, the code is rejected at compile time since it's a static error to import the same binding from two modules. But you're right that the upgrade-hazard is still there. I believe this is a small price to pay for the convenience of rapid scripting.

> I do not understand why the ModuleSpecifier may be a list of strings. A list of strings is less than you need to express where/how to find and verify that a source file may be the thing you intend to designate. Such lookup strategy issue should be internal to the module resolver. I like Ihab's packaging notion of a catalog, used to provide a module resolver with a mapping from simple module ids to potentially complex descriptions of where/how/verify. Given a catalog, a list of strings is more than you need for indexing into a cataloged description. A single module id should be fine.

Allen also made a similar point about not entangling the notion of module identifiers and configuration management / resource location. I agree we should make it possible to separate this out.

At the same time, I think it should be as absolutely dirt-simple as possible to migrate from non-module to module code, as well as to write short scripts. One way to do this is to allow module specifiers to directly indicate how to find a module, and then you can refactor it later to go through the extra indirection of a configuration table of some sort.

For example, you wouldn't want to require every script that uses a couple little libraries to create a separate table e.g.:

    <script type="harmony-module-set">
        module JSON = ["std://JSON", "http://json.org/json.js"];
        module DOM = ["std://DOM"];
    </script>
    <script type="harmony">
        import JSON as JSON;
        import * from DOM;
        alert(JSON.stringify({"hi":"world"}));
    </script>

(No syntax quibbles; it's just for example.)

I'm not saying that's what you're proposing, just that any solution that does provide the ability to separate module identifiers from their location needs to keep the top-level burden at a minimum.

One quick strawman:

    ModuleSpecifier ::= IndirectModuleSpecifier
                     |  DirectModuleSpecifier

    DirectModuleSpecifier ::= StringLiteral
                           |  '[' StringLiteral*(',') ']'

    IndirectModuleSpecifier ::= Identifier
                             |  Identifier '.' IndirectModuleSpecifier

The "direct" syntax would basically tell the host environment how to locate the resource; the "indrect" syntax would look up in some user-provided configuration table. (If it's not user-provided, you have a standard-outside-the-standard problem.)

> For your Module production, what is the purpose of the Id after the "module" keyword? After all, the module resolver is already assumed somehow able to find a named module by other means, such as its location.

So that you can define modules inline instead of requiring them all to be in separate files, which would especially be burdensome on the web. This way you can write:

    <script type="harmony">
        module A { ... }
        module B { ... }

        import A as A; import B as B;
        ...
    </script>

> * Since the most often exported thing will be functions (and in SES the only thing), should we allow 
> 
>     ExportDeclaration ::= 'export' Id '(' Params_opt ')' '{' FunctionBody '}'
> 
> as a shorthand for defining, freezing, and exporting a function?  

Worth considering.

> * By "VariableStatement", do you intend to include "const" and "let" declarations? These cannot be grouped into the same production because
> 
>     if (e1) var x = 1;
> 
> is fine but 
> 
>     if (e1) let x = 1;
> 
> must be disallowed since lets don't hoist.

Good question. I drafted the grammar pretty quickly. These kinds of points probably do need refinement.

> * What is "modes for this"? You never again use "mode" in this manner.

Different language modes. For example, I could imagine a convenient <script> type that gives you some convenient defaults:

    <script type="harmony-script">
        // the stdlib is already in scope
        var now = new Date();
        ...
    </script>

As for `this', I actually didn't remember that I wrote that. :) I'm not sure whether `this' should always be initially null. To my thinking `this' is still TBD.

> * I am very confused by your discussions of cyclic imports. How do you propose that a module instance object's exported property return undefined until its corresponding exported const variable is bound? For exported var variables, this makes sense, since var variables are immediately readable as undefined. For functions, of course, there's no problem; and therefore for SES there's no problem. But const and let variables normally have a read barrier, which throws on early read. I would hope to see this reflected in the module system.

Oh sure, fair enough. That's probably the right way to do it. Although it should perhaps not be possible to export a `let' binding; those really are supposed to be purely local.

> * Is the "can" in
> 
>     But their properties can be explicitly updated in ECMAScript code, they cannot receive new properties, etc.
> 
> a misspelling of "cannot"?

A very inconvenient misspelling! :)

> * In your optimization opportunities, you suggest that an
> 
>     module A {
>       import "B";
>       // do stuff
>     }
> 
> could start executing once the module resolver knows it can resolve B but before it knows whether B itself will have an early error. This implies that the semantics of importing a module with an early error is not that the importing module itself has an early error. What alternative semantics for B's early error do you suggest for A? (There is a parenthetical comment that might be about this at the beginning of "Dynamic module loading". But I don't know since I didn't understand it either.)

I don't think it says that-- you should be able to *compile* them in parallel, and whoever gets the first early error can report it. But you're not allowed to start executing early, unless you pause at the first observable side effect, until everyone's been checked for errors.

[Side note: this stuff gets very subtle; thankfully it's *mostly* the job of the compiler writers. :) Obviously we are responsible for making this sufficiently optimizable, but the point just being none of it reflects semantic complexity, so users don't have to worry about it much. When they care about performance, as long as the best practices are fairly sane, like "don't have too many large cyclic dependencies" and "order your module definitions by dependency so the leaves get loaded first," then I don't think the complexity is too burdensome.]

> * What does the fragment in 
> 
>     import 'http://developer.yahoo.com/modules/yui3.js#dom'
> 
> mean? 

The Yahoo YUI library provides a module called `dom'. The idea of the hash symbol is that browsers might want to standardize on some way of selecting an individual module from a single URL that provides multiple module definitions. Perhaps the hash symbol is too cute; I don't know. This isn't our mandate to standardize; it's meant as a plausible example.

> * What is "offline ES"? Do you mean server-side?

Yep.

Phew! Thanks again for the feedback.

Dave

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20100203/518d4e6c/attachment-0001.html>


More information about the es-discuss mailing list