Composition of Uncoordinated Working Sets of Modules

Kris Kowal kris.kowal at cixar.com
Mon Jun 7 10:35:38 PDT 2010


On Mon, Jun 7, 2010 at 8:37 AM, Sam Tobin-Hochstadt <samth at ccs.neu.edu> wrote:
> On Sun, Jun 6, 2010 at 2:00 PM, Kris Kowal <kris.kowal at cixar.com> wrote:
> ...

Most of this is good clarification, particularly that load
interacts with the exports of the foreign script's implied,
anonymous module scope.  The grammar is clear.  It would be
good for this to be expressed in one of the examples, and
for it to be clarified in the description of semantics that
every script is also an anonymous module from which the
exports are only accessible through the lexical scope
"shadowing" (I assume) and by being bound to a module
through a "load" expression.

> Right.
>
> module M = load "foo.js";
>
> creates a new module M with foo.js as its contents.

This is a point that Ihab clarified for me yesterday
evening that merits bold and emphasis: loaded modules are
not singletons.  You do this to avoid having to compare
MRL's for equivalence, particularly to avoid having to
define equivalence given the potential abundance of edge
cases.

    http://example.com/module?a=10&b=20
    http://example.com/module?b=20&a=10

It's worth noting, and please dismiss the implication that
the approach is necessarily proper and correct, that this is
not a problem for CommonJS modules because the specification
limits module identifiers to a very small subset of
expressible URLs and defers the issue of URLs to the
packaging layer, wherein the semantics are similar to those
put forth here.

>> * for a script to have importable bindings, these must
>> exist in a  module block of the loaded script.
>
> I don't know what this means.

Ihab clarified that this is not true.  This is a
re-statement of my mis-perception that there is no implicit,
anonymous, top-level module in a script and that therefore
there cannot be exports outside explicit module blocks.  I
stand contentedly corrected on this point.


> It's possible to use the module loader API to do this,
> slightly more verbosely.   But why?  If you say:
>
> module A = load "aQuery.js";
>
> then A.$ is already available for use in expression
> contexts.

I can make the same argument about "import *".  If I "import
A", I can access its contents as "A.$".  To permit
destructing on all import expressions would be consistent
philosophically.


>> link.js
>>    module aQuery_ = load("scripts/aQuery.js");
>>    const $ = aQuery_.aQuery.$;
>
> Your example might point to a need to augment the module
> loader api with information on 'load' calls specifying
> what module the 'load' occurs in.

Exactly.  The Narwhal loader receives an id and a baseId on
from require(id) calls.  Each module gets a fresh "require"
effectively bound on the baseId.  I think that the loader
handler needs to receive the base MRL as an argument or part
of the request object.


Another thing that Ihab clarified which merits a full
section on the wiki is the dynamic scoping of lexical module
names.  Ihab pointed out that, when a script is loaded, it
inherits the module scope chain of the declarer, permitting
a certain degree of "aspect oriented" dependency provision,
or "external linkage".  This depends on an understanding
that each "load" always instantiates a module and cannot
ever rebind an existing module (a singleton).

loaded.js
    export function poof(el) {
        $.poof(el);
    }

aQuery.js
    export function poof(el) {
        // one implementation
    }

bQuery.js
    export function poof(el) {
        // an alternate implementation
    }

a.js
    module $ = load("aQuery.js")
    module X = load("loaded.js");

b.js
    module $ = load("bQuery.js")
    module X = load("loaded.js");

linkage.js
    module A = load("a.js");
    module B = load("b.js");

Noting that in this example, having loaded linkage, there
are two instances of "loaded.js", each of which sees "$" as
"aQuery" and "bQuery" respectively.  They also see "X", "A"
and "B" following the dynamic scope chain.

This is something I have not considered.  It would be good
to do a write-up on what use-cases you have in mind for this
feature.

Also note that, because I was not aware of this feature,
I've been using the terms "external linkage" and "internal
linkage" differently, in the context of my first email on
this thread.  I used the terms "internal" and "external" to
refer to modules from a given working set of modules from
one coordinated design (a "package"), and to modules outside
the package, in other packages.  Sorry for the confusion.


At this point I have been convinced that it is possible with
this proposal to integrate uncoordinated working sets of
modules by using the load syntax and script-scoped exports.
I've also been convinced that there is a way to inject free
variables into an isolated context, as mediated by the
loader.  I've been made aware that there exists a way to
implicitly inject modules when loading scripts, which
implies that there is a contract between the loader and
loadee that certain free variables in the loadee will be
bound through the module scope chain.  This provides a finer
grain means of weaving dependencies into a module than that
provided by the global environment record as passed to
instantiate a loader.

It's also clear now that this proposal does use a
three-layered approach (lexically scoped variables, module
identifiers, and URL's), and that it simply differs from
other systems in that module identifiers share the lexical
scope, and URL's only grab "packages" in the sense that an
entire tree of modules can be referenced through transitive
"loads".  I'm going to mull the implications, but one for
sure, is that it is necessary to buy a whole package even if
you only want a single function from it.  Tooling like
prototype's Sprockets might address this.

I have some outstanding "side-show" points for refining the
proposal:

* load handlers need to receive the MRL of the declaring
  module so they can resolve relative MRLs.  This one's
  important.
* there should be cleaner syntax for destructuring a loaded
  module.  Preferably whether a module is rebound or loaded
  should be orthogonal to the destructuring syntax.  Sugar.
* the community should be called upon to weigh in on whether
  "import *" should be supported, and we should frame the
  question with education on the full implications of the
  trade-off.
* we should consider a way to link one loader to another,
  such that a loader, for example a package loader, can be
  mapped responsibility for all modules in a subtree of the
  MRL name space without having to communicate exclusively
  in source strings.

I would like to see a lot more examples of solving problems
on the wiki.

Thanks to everyone for the clarifications,

Kris Kowal


More information about the es-discuss mailing list