Composition of Uncoordinated Working Sets of Modules

Kris Kowal kris.kowal at cixar.com
Sat Jun 5 14:17:11 PDT 2010


On Sat, Jun 5, 2010 at 3:40 AM, Sam Tobin-Hochstadt <samth at ccs.neu.edu> wrote:
> On Fri, Jun 4, 2010 at 9:48 PM, Kris Kowal <kris.kowal at cixar.com> wrote:
>> On Fri, Jun 4, 2010 at 5:17 PM, David Herman <dherman at mozilla.com> wrote:
>>> By keeping modules second class, we get a number of benefits, not
>>> just handling cyclic dependencies. (In fact, cyclic dependencies can
>>> be handled nicely in a first-class module system as well.) One of
>>> the benefits of second-class modules is the ability to manage static
>>> bindings; for example, import m.*; is statically manageable. Allen
>>> has made some good points about how second-class modules are a good
>>> fit for the programmer's mental model of statically delineated
>>> portions of code. At any rate, cyclic dependencies are not the
>>> central point.
>>
>> As far as I can tell, Simple Modules only changes the composition
>> hazard introduced by "imoprt m.*" from a run-time hazard to a
>> link-time hazard.
>
> In your example, certainly the earlier error is a benefit of our
> proposal.

I strongly disagree.  Either Alice is at fault for using "import m.*",
Charlie is at fault for altering her API, or neither Alice and Charlie
are at fault because they were merely and earnestly using the features
of the underlying system, in which case the system is at fault.  Alice
should be able to trust the features of her module system.  Charlie
should be able to *augment* her API without breaking her dependents.

> But the really key benefit is this:
>
> module M {
>  export x = 7;
> }
>
> module N {
>  M.y + 3; // an error - just like an unbound variable in ES5 strict
> }

This feature does not preclude the omission of the import * syntax
variant.

> This can be an early error because we statically know a lot about
> modules.  This is good for programmers, because it supports early
> errors, and also good for compiler writers, since it supports
> optimization.

I agree.  I do not think that any of my objections preclude statically
linking name spaces.

> Because Simple Modules is based on lexical scope, collecting modules
> is as simple as collecting objects into a larger object:
>
> module Container {
>  module Sub1 = load "http://example.com/foo.js";
>  module Sub2 = Other.InnerModule;
>  module Sub3 {
>    module SubSub4 = load "http://example.org/bar.js";
>  }
> }

And I presume that usage of submodules is:

    module Container = load("the script above");
    module X = Container.Sub1.SubSub4;

If that's the case, please consider making the nested module export
explicit:

    module Container {
        export module Contained {
        }
    }

I am not attached to the name spaces feature of the Simple Modules
proposal, but it's not worth fighting.

>>> This is also not true; the ability to attach modules to module
>>> loaders (as well as the dynamic evaluation methods) makes it
>>> possible for separate module loaders to communicate. However,
>>> loaders aren't about linking multiple working sets, but rather
>>> providing isolated subspaces. (One use case I sometimes use is an
>>> IDE implemented in ES, that wants to run other ES programs without
>>> them stepping on its toes.)
>>
>> Code examples would be insightful.
>
> Currently, in web-based IDEs such as Bespin, code being developed has
> the ability to muck with the internal state of the IDE and the overall
> page, which is usually undesirable.  With module loaders, simply by
> not sharing access to the DOM or other internal state with the code
> being developed, this would be prevented.

I understand and wholeheartedly agree with the "why".  I do not
understand "how".  Code examples would be insightful.

>> Perhaps I am misunderstanding the scope of a module name.  Is it not
>> true that a module is available by its self declared name in all
>> modules that share a loader?  Is it actually possible to bind a single
>> module name that provides access to all of the modules in another
>> loader?
>>
>>    module X = load("http://example.com/api");
>>    module Y = X.Y; // is this possible?
>
> Yes, if that URL has a module Y in the code that it provides.  For
> example, if that URL produces the code:
>
> module Y { ... }
> module Z { ... }
>
> Then it's certainly possible.

Developers should not need to concatenate subsystems to construct
packages.  It should be possible for one to connect loaders to other
loaders.

>> Is it possible for MRL's to be CommonJS top-level and relative module
>> identifiers?
>
> We've avoided committing to particular syntax for MRLs so far,
> although the discussion at the last meeting tended toward the
> following syntax:
>
> MRL = URL | RelativeURL | "@"Identifier

This notation seems adequate.  The question remains whether relative
URL's are supported by the proposed loader API.  It's my impression
that it is not presently possible for a loader handler to observe the
MRL of the module that requested the module.  If that's the case, it
would be the responsibility of the loader itself to resolve MRL's.  It
would be better if that responsibility were deferred to the loader
handler.

>> If that's the case, is it possible for the loader
>> handler to forward a request for a module to another loader?
>>
>>    var externalLoaders = {};
>>    Loader(function (id, request) {
>>        var parts = id.split("/");
>>        if (parts[0] === "." || parts[0] === "..") {
>>            // what is the identifier of the module from which this
>>            // module was requested?  I need that to resolve the
>>            // identifier of the request module.
>>            when(fetch(id),
>>                request.provideSource,
>>                request.reject
>>            );
>>        } else {
>>            var external = externalLoaders[parts[0]];
>>            request.provideLoader(external, parts.slice(1).join("/"));
>>        }
>>    })
>
> This could work, certainly.  Note that there's no particular need for
> the externalLoaders to be ModuleLoaders themselves - they could have
> whatever API is useful in this context.

It would still be nice if you could just connect loaders to loaders to
so they could share the results of compilation and static analysis
instead of having to communicate entirely with source code.

>> I think it might be best to organize the syntax around MRL's rather
>> than local short-names.  MRL's can be reasonably short if they're
>> permitted to be relative paths, which requires the module loader
>> handler to receive the MRL of the requesting module.
>
> This is one thing we've resolutely tried to avoid.  A key aspect of
> our module system is that it gets sharing right - if you import a
> module in two different places, that module is shared.  This requires
> knowing when you import "the same" module.  In most languages, this
> ultimately comes down to some filesystem-based comparison, which we
> don't have the luxury of on the web.  MRLs don't support a very good
> equality operation. That's why we've gone simply with names, with a
> very simple equality.

This strikes me as six of one and half a dozen of the other.  MRL's
are just as comparable as your short names and neither guarantee
source equivalence or semantic equivalence.  It's really not worth
trying, even on a file system.  If you want the same thing, you either
have to use the same name or the same MRL.

I'm not convinced that using short names will be or need to be the
common case.  In CommonJS, "MRL's" are sufficient.  It's certainly not
worth the complexity cost to have two layers of naming.

Kris Kowal


More information about the es-discuss mailing list