Module linking (Was: The global object should not be the "global scope instance object")

Allen Wirfs-Brock allen at wirfs-brock.com
Mon Jan 30 15:50:05 PST 2012


On Jan 30, 2012, at 12:09 PM, Andreas Rossberg wrote:

> (Just a quick reply, because I'm in a bit of a hurry.)
> 
> On 30 January 2012 19:17, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:
>>>> 
...
>>>> 
> 
> The analysis is not as straightforward as it might seem. In particular, this:
> 
>> The exported name list of a module can be generated while the module is parsed
> 
> unfortunately is not true, because of (1) alias bindings, and (2)
> import *. For example, with
> 
>  module A = B.C

Dave/Sam, it would be nice if we had up to date syntax at http://wiki.ecmascript.org/doku.php?id=harmony:modules 

> 
> you won't know what A exports before you haven't analysed B and its
> nested C, which might come later in the program. And those can
> recursively refer to A, which might require you knowing what A
> exports. Here is a more messed up, random example:
> 
> 1  module A = B.C
> 2  module D { ... }
> 3  module B {
> 4     let x = B.E.x
> 5     import * from A
> 6     export module B {
> 7       export module E = D
> 8       export module C {
> 9         export module D { ... }
>      }
>    }
>  }

I added line numbers above for reference.  I think there is a basic bug in the above that may just be a typo.  "B" reference in line 1 must refer to the module defined in line 3.  Hence "B.C" must refer to an export of "C" from that module. However, the only export from that module is line 6 which exports "B" so the "B.C reference in line 1 is invalid.  This does require any circular analysis and line 5 doesn't impact it. 

To get around this  this issue, I looked at assuming that line 6 wasn't really intended to be there but that seems to break your example in other ways.  What did you really have in mind?

> 
> You couldn't tell which D is aliased by E before processing C and so
> on. In general, it's all recursive, so a simple AST traversal alone,
> in whatever order, won't do. Instead you need to collect and solve
> constraints. In terms of type inference, you infer and unify
> (principal) types.

I agree that * imports complicate matters.  I thought there had been discussion at some point about eliminating them, but I'm not yet convinced that by themselves they turn this into a constraint solving problems.  In stead I think that we need appropriate static restrictions to ensure that it can never be such a problem.  If it takes solving to understand module compositions then we have created a too power model of module composition.  If the module linkages aren't pretty damn obvious to both the writer and the reader, then we've done something wrong.

I'm actually not a bit fan of using nesting to do module wiring, but I don't think it has result in needing constraint solving to understand the wiring.

> 
> 
>>>> 2) module instantiation is performed for the block.  This instantiates each
>>>> module defined by the top level of the block, instantiating a module
>>>> includes producing the list of identifiers exported by the module. Each
>>>> identifier is associated with a new uninitialized binding. Instantiated
>>>> modules are not initialized (their body is not executed) at this time.
>>> 
>>> You need a new uninitialized binding for all identifiers in the
>>> module's local scope, not just the exported ones.
>> The non-exported declarations within a module don't need to be instantiated until module initialization. You probably could instantiate them durning my module instantiation step but I'm trying to only identify what must happen at that step.

I was really thinking about non-module declarations  when I wrote the above statement.  Module alias declarations like in these examples do lead to a more complex traversal.  I have to think about is some more.

> 
> I'm not so sure. You could export parts of a local module:
> 
> module A {
>  module B { export module C { ... } }
>  export module C = B.C
> }
> module C = A.C
> 
> 
>>>> 3) An initialized binding for "a" is is created in the top level environment
>>>> for the script.  (all top level binding are instantiate at this point, if
>>>> there were any others).  Note that the binding for a is initialized (it
>>>> reference a module instance object) but the module itself is not yet
>>>> initialized
>>>> 4) initialize module a
>>>>      5) module instantiation is performed for the body of module a.  This
>>>> instantiates a module instance for module b with exported identifier "y" and
>>>> its binding.
>>> 
>>> It's not quite that simple. Recursive initialization does not suffice,
>>> because there can be arbitrary import/export/aliasing cycles (e.g., b
>>> could import from a as well). Consequently, you generally need to
>>> create the instance object for _all_ modules in the program first,
>>> before you can start initializing _any_ of them (or the toplevel, for
>>> that matter).
>> 
>> I'm trying to demonstrate an algorithm that takes care of this.  Give me a specific example that you don't think works.
> 
> If I understand your description correctly, then consider this:
> 
>  module A { export module C = B.C; export module D { ... } }
>  module B { export module C { ... }; export module D = A.D }

Sold, it certainly does look like that modules need to be identified and linked before evaluation starts.

that feels to me link a distinct pass.

Allen
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20120130/544f04e4/attachment-0001.html>


More information about the es-discuss mailing list