Modules, Concatenation, and Better Solutions

David Herman dherman at mozilla.com
Tue Oct 16 19:10:24 PDT 2012


On Oct 16, 2012, at 4:51 PM, John J Barton <johnjbarton at johnjbarton.com> wrote:

> On Tue, Oct 16, 2012 at 2:58 PM, David Herman <dherman at mozilla.com> wrote:
>> The weird thing about (a) is that code that appears to be straight-line actually executes in somewhat more unpredictable (although deterministic) order.
> 
> I guess if we can handle deeply nested callbacks, event handlers, and
> promises, then this is a pretty tame as weird things go.  Are you are
> saying is that the body of the module does not run until we need its
> content? So dependents pull in their dependencies and modules that are
> not needed are not executed. That's what we want right?

My main concern comes from the fact that module initialization code can have arbitrary side effects: modify variables or shared objects, update the DOM, change some CSS, send an XHR request to fire a nuclear missile (HTTP UPDATE please -- I like my global thermonuclear war RESTful), ... You don't generally want languages to cause side effects to happen in unpredictable orders. If modules are executed on demand, you can't as easily predict when they are going to execute. (This is an instance of the general rule of PL design: lazy execution and side effects don't mix.)

But, that said, the execution of the modules isn't triggered by arbitrary control flow of the program, just the syntactically restricted top-level imports. So that probably makes it pretty tame.

Still, I'm interested in hearing Sam's and Andreas Rossberg's input on this.

>> The downside of (b) is that when you have cyclic dependencies, one module won't even be *partly* initialized before the other module starts running. (Although usually, when you have cyclic dependencies, you probably don't want to be evaluating exports from each other in top-level code -- you want all those mutual references to be happening under functions.)
> 
> I don't understand b, but dependency on internal vs external sounds bad.

Both (a) and (b) are meant to avoid dependency on internal vs external (unlike the system as currently described on the wiki). I didn't explain (b) very well. The idea would be that you would execute modules one at a time, rather than interleaved, but their order of execution would be topologically sorted by dependency. So if you don't have cyclic dependencies, you know that by the time you refer to an export of a module you depend on, it will have already been initialized. However, with cyclic dependencies, it's harder to understand what order they'll execute in, and you need to make sure not to refer to exports of a module until it's been fully initialized. By contrast, with (a), there's still a chance that you won't blow up.

Concrete example: Even and Odd modules refer to each other, but the import statements occur after some initialization:

    module Odd {
        export let odd = function(x) {
            return x === 0 ? false : !even(x - 1);
        }
        import even from Even; // force execution of Even here, if it hasn't already
        export let b = odd(17);
    }
    module Even {
        export let even = function(x) {
            return x === 0 || !odd(x - 1);
        }
        import odd from Odd; // force execution of Odd here, if it hasn't already
        export let b = even(17);
    }
    console.log(Odd.b);

With semantics (a), this executes like so:

- start executing Odd
- initialize Odd.odd
- start executing Even
- initialize Even.even
- try to start executing Odd (but it's already started, so don't)
- initialize Even.b by calling Even.even
- initialize Odd.b by calling Odd.odd

And everything succeeds. With semantics (b), regardless of how we break the tie in a cycle, this will fail, because it will try to call either Odd.odd or Even.even before it has initialized the function. Say it starts executing Odd first. It would do this:

- start executing Odd
- initialize Odd.odd
- initialize Odd.b by calling Even.even
- error: Even.even is not yet initialized

So semantics (a) makes it more possible to make a cyclic dependency work, but it's still subtle if you're trying to let them refer to each other in top-level initialization code: you have to make sure to carefully place the imports late enough that the relevant pieces of each modules will have adequately initialized.

> jjb ... eager to see modules happen!

Me too! :)

Dave



More information about the es-discuss mailing list