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

_______________________________________________
es-discuss mailing list
es-discuss@mozilla.org
https://mail.mozilla.org/listinfo/es-discuss

Reply via email to