simple modules

Brendan Eich brendan at mozilla.com
Wed Feb 3 12:00:05 PST 2010


On Feb 2, 2010, at 5:16 PM, Mark S. Miller wrote:

> * 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 ';'

Hi Mark, I too had a few bikeshed/usability/redundancy thoughts  
similar to yours on syntax. The color of the paint is the least of  
these three issues, so this seems worth discussing (below).


> 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.

Just a n00b thing ;-) -- I took the liberty of fixing this.


> 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.
>
>    Your....                    Becomes....
>
>    import "Math" as M          import M from Math
>    import "Math": sum, pi      import {sum, pi} from Math
>    import "Math": sum as sam   import {sum: sam} from Math
>

I too advocated import ... from M; in conversations with Dave. It's  
prettier. The destructuring binding analogy only goes so far, though,  
since the module id does not denote an object (these are second class  
modules in this context) and indeed (Allen's suggestion) may be a  
special form not similar to any object expression.

Also, the braces are a bit much, especially with * (below). The []  
array pattern does not make sense, so it is plausible to drop the  
requirement for outermost braces.

It's also plausible to keep them, as much for intuitive precedence of  
"from" over "unbraced comma" as for consistency with destructuring.  
What I'm getting at is this: comma is the lowest precedence operator,  
and when used as a separator (e.g. in actual parameter lists) it  
separates higher-precedence AssignmentExpressions. Yet

import x, y as yy, z from M;

while unambiguous and parseable according to a sound grammar still may  
be too different in its use of comma, |as| with higher precedence, and  
|from| with lower precedence than comma.

It's a fine point; we should try writing examples this way often and  
see where the usability sweet spot is.


>    import "Math"               import * from Math

Right.

I see Allen's argument against the quoted-string module id as  
attractive nuisance factored into your "Becomes..." alternatives, and  
that's great. More to strawman-specify here, but I believe everyone  
agrees with Allen's point. (I hope we can avoid the hated :: separator  
in the special form for module ids, however! Yet . also has issues --  
more later.)


> With a ModuleSpecifier being a single module id, it could be an Id  
> production rather than a StringLiteral (as also shown above).

I believe we'll need more than a flat Identiifier to denote modules  
when internally linking. This gets at the next point, however:


> 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.

Simple modules do not want to require external linking, catalogs, etc.  
Part of the simplicity is starting small and growing, just by using  
files or script tags depending on your embedding of the language.  
Doing so means it should be possible to specify module ids when  
defining as well as when importing.


> * 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?

I remember your examples in the wiki doing a similar thing with const.  
It might be the right short-hand, including freezing. It's pretty  
sweet, although using 'let' or 'var' to define a function without the  
'function' keyword, e.g.

   let f(a,b,c) { return a*b + c; }

looks like "a bridge too far" to me: is f hoisted to top of block, or  
to top of program if no braced block encloses this definition, but  
given the initial value |undefined|? 'function' gives a letrec binding  
without linking the related functions and it's old as the hills.

I've found OCaml's allowing let to bind a function less helpfully  
different than SML's separate fun binding form, when reading and  
searching code. This is the practical side of "different meanings  
should have different syntaxes". It matters more than epsilon for  
programmer productivity in my experience.

We need to avoid too much redundancy among short- and long-hands. We  
should avoid symmetry breaks where the shorter form has confusingly  
different meaning. But the latter point does not argue against  
implicit freezing in the case of |export f(...) ...|, IMHO.


> * 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 catch; I think the VariableStatement referenced was ES5's or  
ES1-3's for that matter. Need to split cases for 'let'.


> * 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.

Good point.


> I would hope to see this reflected in the module system. For  
> example, if a
>
>     export const x = 8;
>
> were in effect
>
>     // at top of module
>     Object.defineProperty(magicPreregisteredExports, 'x', {
>       get: const() {return x;},
>       enumerable: true
>     });
>
>     // at original location
>     const x = 8;
>
> then an early attempt to read this property from the module instance  
> object -- including by pattern matching

Nit, not nagging but this is important: destructuring is not pattern  
matching, critically because of the lack of alternative choice, cut,  
etc. Indeed destructuring will happily bind |undefined| if you ask for  
a missing property name or index.

True matching, for things like JSON and AST processing, could be  
valuable as a future extension; separate topic, more later.


> -- would throw rather than silently give the wrong value.

I'll leave this question for Sam&Dave but I agree that the module  
instance object would have to implement a read barrier in order to  
throw for early access to const and let, as you suggest.


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

Fixed.


> * 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.)

Seems early errors should be well-ordered and blamed on the directly- 
erroneous module, so we can't proceed with evaluating A until B has  
been evaluated. But this is likely, given that a resolved B will have  
been parsed when loaded, in common implementations. The spec should be  
normative here, I agree.

I was chatting to Maciej about all this and he asked whether eval(s)  
where s contains import M is allowed. If M can't be prefetched this  
would be a blocking import within eval, violating the execution model  
in the face of nested event loops, mutation from other events, etc. Or  
it would be a "modal" blocking import, like a modal dialog -- even  
worse.

Probably import should not be allowed in eval code.


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

I read this as more plausibility-argument sketching, not (yet) a  
detailed proposal. If we don't think it should be developed into a non- 
sketchy proposal, it should be cut.


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

Or disconnected client, I took it to mean.

/be

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


More information about the es-discuss mailing list