Nested modules clarification
On Jul 17, 2012, at 9:17 PM, ๏̯͡๏ Jasvir Nagra wrote:
However, Module Semantics (harmony:modules_semantics) suggests that each inline module declaration introduces a fresh scope.
As in, a fresh nested scope.
Given this, I am trying to understand whether there is any difference in semantics between nested rather than sibling modules.
Yes. They can refer to outer bindings.
For example, is there any semantic difference between the following: module widgets { export module button { ... } export module alert { ... } export module textarea { ... } ... } import { messageBox, confirmDialog } from widgets.alert; and module widgets.button { ... } module widgets.alert { ... } module widgets.textarea { ... } ...
import { messageBox, confirmDialog } from widgets.alert;
...other than the change to the grammar that module name can now contain a ".".
Well, you didn't tell us what the semantics of your new syntax is. :) But it seems to suggest that anyone anywhere could add to a module. The declarative syntax is meant to keep the exports of a module local to its body. Allowing anyone anywhere to extend a module runs counter to the idea of modularity.
On Tue, Jul 17, 2012 at 11:21 PM, David Herman <dherman at mozilla.com> wrote:
On Jul 17, 2012, at 9:17 PM, ๏̯͡๏ Jasvir Nagra wrote:
However, Module Semantics ( harmony:modules_semantics) suggests that each inline module declaration introduces a fresh scope.
As in, a fresh nested scope.
Ah that was definitely not clear to me from reading the spec or the examples. What outer bindings can be referred to - any lexically scoped variables or just other module definitions?
Given this, I am trying to understand whether there is any difference in semantics between nested rather than sibling modules.
Yes. They can refer to outer bindings.
For example, is there any semantic difference between the following:
module widgets { export module button { ... } export module alert { ... } export module textarea { ... } ...}import { messageBox, confirmDialog } from widgets.alert;
and
module widgets.button { ... } module widgets.alert { ... } module widgets.textarea { ... } ... import { messageBox, confirmDialog } from widgets.alert;
...other than the change to the grammar that module name can now contain a ".".
Well, you didn't tell us what the semantics of your new syntax is. :) But it seems to suggest that anyone anywhere could add to a module. The declarative syntax is meant to keep the exports of a module local to its body. Allowing anyone anywhere to extend a module runs counter to the idea of modularity.
I wasn't suggesting new semantics - just trying to clarify what I thought the draft was saying ie. that a completely fresh scope was created. If that were the case, nesting wouldn't buy you much other than perhaps syntactical organization.
On Wed, Jul 18, 2012 at 2:37 AM, ๏̯͡๏ Jasvir Nagra <jas at nagras.com> wrote:
On Tue, Jul 17, 2012 at 11:21 PM, David Herman <dherman at mozilla.com> wrote:
On Jul 17, 2012, at 9:17 PM, ๏̯͡๏ Jasvir Nagra wrote:
However, Module Semantics (harmony:modules_semantics) suggests that each inline module declaration introduces a fresh scope.
As in, a fresh nested scope.
Ah that was definitely not clear to me from reading the spec or the examples. What outer bindings can be referred to - any lexically scoped variables or just other module definitions?
Lexical bindings as well as module definitions:
module outer { let x = 1; export module inner { export y = x + 2; } }
On Wed, Jul 18, 2012 at 4:25 AM, Sam Tobin-Hochstadt <samth at ccs.neu.edu>wrote:
On Wed, Jul 18, 2012 at 2:37 AM, ๏̯͡๏ Jasvir Nagra <jas at nagras.com> wrote:
On Tue, Jul 17, 2012 at 11:21 PM, David Herman <dherman at mozilla.com> wrote:
On Jul 17, 2012, at 9:17 PM, ๏̯͡๏ Jasvir Nagra wrote:
However, Module Semantics (harmony:modules_semantics) suggests
that each inline module declaration introduces a fresh scope.
As in, a fresh nested scope.
Ah that was definitely not clear to me from reading the spec or the examples. What outer bindings can be referred to - any lexically scoped variables or just other module definitions?
Lexical bindings as well as module definitions:
module outer { let x = 1; export module inner { export y = x + 2; } }
That's a little unexpected. Would the same hold if the above code was refactored so the inner module was a separate file?
module outer { let x = 1; export module inner = "m/inner.js"; }
If not, should a module inliner be doing variable renaming in order to maintain semantics?
On Jul 18, 2012, at 9:15 AM, ๏̯͡๏ Jasvir Nagra wrote:
On Wed, Jul 18, 2012 at 4:25 AM, Sam Tobin-Hochstadt <samth at ccs.neu.edu> wrote:
Lexical bindings as well as module definitions:
module outer { let x = 1; export module inner { export y = x + 2; } }
That's a little unexpected. Would the same hold if the above code was refactored so the inner module was a separate file?
module outer { let x = 1; export module inner = "m/inner.js"; }
No, an external file only has access to the global scope, so x would not be visible to the module in the external file.
If not, should a module inliner be doing variable renaming in order to maintain semantics?
It shouldn't be much of an issue if they're only being inlined at top level. E.g., transforming module files m1.js, m2.js, m3.js into a single file:
module m1 { /* contents of m1.js */ }
module m2 { /* contents of m2.js */ }
module m3 { /* contents of m3.js */ }
Should be exactly equivalent except for the names m1, m2, and m3. So you'd only need to synthesize fresh variable names for the generated modules.
But maybe I'm missing what kind of inliner you're thinking of?
On Wed, Jul 18, 2012 at 9:35 PM, David Herman <dherman at mozilla.com> wrote:
On Jul 18, 2012, at 9:15 AM, ๏̯͡๏ Jasvir Nagra wrote:
On Wed, Jul 18, 2012 at 4:25 AM, Sam Tobin-Hochstadt <samth at ccs.neu.edu>wrote:
Lexical bindings as well as module definitions:
module outer { let x = 1; export module inner { export y = x + 2; } }
That's a little unexpected. Would the same hold if the above code was refactored so the inner module was a separate file?
module outer { let x = 1; export module inner = "m/inner.js"; }
No, an external file only has access to the global scope, so x would not be visible to the module in the external file.
Awesome - that avoids one kind of lexical surprise and I agree is the right design choice. Given the similarity of semantics, I suspect I'd end up replacing a lot of code that currently looks like (function() { ...code... })(); with module $$ { ...code... }. ***
The inlining/outlining issue I was alluding to is because of the deviation from Tennents correspondence and arises dealing with local vars shadowing objects in the global scope. This will affects primodial objects like Object, Array etc. and those that some environments like the browser cause to be injected into the global scope (eg getComputedStyle, location, document, window etc).
Here's the modified test case from earlier:
outer.js
module outer { let Object = {}; export module inner = "m/inner.js"; }
inner.js
Object.prototype.myMarker = 3; assert( ({}).myMarker === 3 )
Presumably, the author of inner.js expects the assertion to hold and it does in the external module case but does not after inlining of the form you describe below. This kind of confusion affects all globals that can be mentioned by an external module that can be used with them being declared.
This brings me to the question of what globals are in scope in a new module? In the case of browsers, there's a lot of globals already. Is the expectation that browser module authors will have to do a:
import {document, window, getComputedStyle,location} from browser;
to get to the document object? How about the window object?
I realize some of this is outside the scope of this standards body but it will affect globals that es standardizes as well. Also, more generally I'd be keen to hear your input on how you think this should even if it's not normative.
jas
*** After writing this, I realized that the module { ... } syntax is (a) unlike (function() {})() is not generative and (b) can't appear in all the places that (function(){})(); can so maybe this is not an entirely good idea. Just seemed nice to have a simpler syntax for it given how close it was to the needed semantics.
If not, should a module inliner be doing variable renaming in order to
On Thu, Jul 19, 2012 at 2:58 PM, ๏̯͡๏ Jasvir Nagra <jas at nagras.com> wrote:
Here's the modified test case from earlier:
outer.js
module outer { let Object = {}; export module inner = "m/inner.js"; }
inner.js
Object.prototype.myMarker = 3; assert( ({}).myMarker === 3 )
A better inlining mechanism is:
compiled.js
module $temp { Object.prototype.myMarker = 3; assert( ({}).myMarker === 3 ) } module outer { let Object = {}; export module inner = $temp; }
This way multiple module foo = "m/inner.js" only leads to one module and you get less scope issues.
On 20 July 2012 01:36, Erik Arvidsson <erik.arvidsson at gmail.com> wrote:
On Thu, Jul 19, 2012 at 2:58 PM, ๏̯͡๏ Jasvir Nagra <jas at nagras.com> wrote:
Here's the modified test case from earlier:
outer.js
module outer { let Object = {}; export module inner = "m/inner.js"; }
inner.js
Object.prototype.myMarker = 3; assert( ({}).myMarker === 3 )
A better inlining mechanism is:
compiled.js
module $temp { Object.prototype.myMarker = 3; assert( ({}).myMarker === 3 ) } module outer { let Object = {}; export module inner = $temp; }
This way multiple module foo = "m/inner.js" only leads to one module and you get less scope issues.
The main problem with this is that it changes the order of effects. Of course, it is not recommended for a module body to have observable effects, so maybe it's fine if an inliner ignores that issue and does not guarantee semantic equivalence for such cases.
On Fri, Jul 20, 2012 at 1:47 AM, Andreas Rossberg <rossberg at google.com>wrote:
On 20 July 2012 01:36, Erik Arvidsson <erik.arvidsson at gmail.com> wrote:
On Thu, Jul 19, 2012 at 2:58 PM, ๏̯͡๏ Jasvir Nagra <jas at nagras.com> wrote:
Here's the modified test case from earlier:
outer.js
module outer { let Object = {}; export module inner = "m/inner.js"; }
inner.js
Object.prototype.myMarker = 3; assert( ({}).myMarker === 3 )
A better inlining mechanism is:
compiled.js
module $temp { Object.prototype.myMarker = 3; assert( ({}).myMarker === 3 ) } module outer { let Object = {}; export module inner = $temp; }
This way multiple module foo = "m/inner.js" only leads to one module and you get less scope issues.
The main problem with this is that it changes the order of effects. Of course, it is not recommended for a module body to have observable effects, so maybe it's fine if an inliner ignores that issue and does not guarantee semantic equivalence for such cases.
I quite liked Erik's suggestion (the Arvidsson transform?) - it's clever. Is the cleverness made necessary because the lexical scope surrounding a module is captured by a module. As a result, the compiler is forced to compile nested modules to a place where there are no variables to accidentally capture ie. the "top-level" of a module. In Sam and Dave's original module proposal, IIRC modules were a lexical scope cut-point and thus did not have this issue.
(PS. I suspect it's clear but I'll say it anyways - I don't have a particular approach I prefer - the modules proposals have a lot of details and consequences that were non-obvious to me from the wiki writeup. I am hoping that in asking these questions, I am helping clarify them for everyone).
On Fri, Jul 20, 2012 at 10:23 AM, ๏̯͡๏ Jasvir Nagra <jas at nagras.com> wrote:
I quite liked Erik's suggestion (the Arvidsson transform?) - it's clever. Is the cleverness made necessary because the lexical scope surrounding a module is captured by a module. As a result, the compiler is forced to compile nested modules to a place where there are no variables to accidentally capture ie. the "top-level" of a module. In Sam and Dave's original module proposal, IIRC modules were a lexical scope cut-point and thus did not have this issue.
Just for clarification, this isn't correct -- the proposal has always had inner modules inheriting outer scopes. In the past even references to external modules inherited the scope, but we removed that because it produces highly confusing results.
On Fri, Jul 20, 2012 at 7:30 AM, Sam Tobin-Hochstadt <samth at ccs.neu.edu>wrote:
On Fri, Jul 20, 2012 at 10:23 AM, ๏̯͡๏ Jasvir Nagra <jas at nagras.com> wrote:
I quite liked Erik's suggestion (the Arvidsson transform?) - it's clever. Is the cleverness made necessary because the lexical scope surrounding a module is captured by a module. As a result, the compiler is forced to compile nested modules to a place where there are no variables to accidentally capture ie. the "top-level" of a module. In Sam and Dave's original module proposal, IIRC modules were a lexical scope cut-point and thus did not have this issue.
Just for clarification, this isn't correct -- the proposal has always had inner modules inheriting outer scopes. In the past even references to external modules inherited the scope, but we removed that because it produces highly confusing results.
Ok got it. Just summarizing my original questions:
-
What is part of the fresh module scope? Sam has clarified that it closes over lexically scoped variables and Dave has clarified that no variables are closed over in the external case. What else is included? Can an external module utter window? In a browser, do you expect it to be able to utter document, getComputedStyle, location? Can modules side-effect these globals or is that verboten and the only expected way for well-behaved modules to return values is via export?
-
Given the semantic hazard possible in inlining, is it worth tweaking the proposal to address it or is the expectation that to really do code consolidation into a single file from separate modules, modules require something more sophisticated that a braindead inliner?
On Jul 20, 2012, at 2:01 PM, ๏̯͡๏ Jasvir Nagra wrote:
- What is part of the fresh module scope? Sam has clarified that it closes over lexically scoped variables and Dave has clarified that no variables are closed over in the external case. What else is included? Can an external module utter window? In a browser, do you expect it to be able to utter document, getComputedStyle, location? Can modules side-effect these globals or is that verboten and the only expected way for well-behaved modules to return values is via export?
Internal modules (i.e., modules declared via module x { ... }
) inherit the entire lexical environment.
External modules inherit only the global scope of their loader, i.e., the global object. In the browser, that means external modules loaded via the standard loader have full access to window, document, etc. in scope.
An external file can dynamically modify the window object however it wants, but it's in strict mode so unable to assign to unbound variables.
Moreover, with static variable checking of all code inside of modules, even if code dynamically adds a new property to the window, that variable is not available at compile time and rejected as unbound. For example, this is an early error:
<script>module m { window.foo = 12; console.log(foo) }</script>
- Given the semantic hazard possible in inlining, is it worth tweaking the proposal to address it or is the expectation that to really do code consolidation into a single file from separate modules, modules require something more sophisticated that a braindead inliner?
Erik's approach isn't clever, it's necessary. The braindead inliner you are talking about would wreak havoc if more than one part of the program binds the external file as a local module. You'd end up duplicating that module's contents in multiple places in the program! The point of having a single instance of each external module is to avoid multiply instantiating it even when it's used in multiple places. So an inliner would have to respect that.
Module Rationale ( harmony:modules_rationale) suggests that
This makes it convenient to modularize code cheaply, without having to create separate files. It is also consistent with the semantics that file top-level is implicitly a module but can contain sub-modules. Finally, it makes refactoring work well: code separated into modules can itself be separated into a new parent module.
However, Module Semantics ( harmony:modules_semantics) suggests that each inline module declaration introduces a fresh scope. Given this, I am trying to understand whether there is any difference in semantics between nested rather than sibling modules.
For example, is there any semantic difference between the following:
module widgets { export module button { ... } export module alert { ... } export module textarea { ... } ...}import { messageBox, confirmDialog } from widgets.alert;
and
module widgets.button { ... } module widgets.alert { ... } module widgets.textarea { ... } ... import { messageBox, confirmDialog } from widgets.alert;
...other than the change to the grammar that module name can now contain a ".".
-- Jasvir Nagra