[e-lang] Module loading discussion

ihab.awad at gmail.com ihab.awad at gmail.com
Thu Nov 19 18:10:33 PST 2009


Retweeting a message from google-caja-discuss to e-lang.

     *   *   *   *   *

Caja fans,

A couple days ago, I presented to the Caja group some material that I had
previously presented to the ECMA TC39 committee, describing the module
system we have implemented in Caja. The slides are here:

  http://sites.google.com/site/ihabawad/Home/es5Modules-2009-11-06.pdf

     *  *   *   *   *

At the Cajita level, we implement a module loader such that the result of
calling:

  load('foo')

where "foo" is a module ID, returns a "module function". This is a closed
function (i.e., it has no free variables) and is instantiated by calling it
with an object literal providing bindings for its free variables. So if
"foo.js" contained, say:

  x + y;

then the following expression:

  load('foo')({ x: 3, y: 4 });

would evaluate to 7. So far so good.

One desideratum is that the IDs of modules are "self-relative". Let's say
module "a/b/c.js" contains the following expression:

  load('../d/e');

This should be relative to its _own_ ID; hence, the result of this should be
to load:

  a/d/e.js

This means that a module must "know" its own ID, in some sense, and hand it
to its own loaders so that they can compute IDs relative to that.

The way we did that in Cajita, we just _gave_ the module access to its own
loader. But Mark Miller pointed out correctly that this was a violation of
the assumption that module functions are transitively immutable -- i.e.,
powerless. If a module function is connected to something that, at the mere
utterance of "load()", goes out to the internets and fetches guff, that is
definitely an ambient authority. So, what's to do?

The simplest way to fix this is to allow a module to know its own ID (i.e.,
the ID it was loaded by). Let's say each module function is given a
well-known constant in its lexical scope; for this description, we will call
it:

  _thisModuleId_

A module can then load something relative to its own ID by saying:

  load('foo', _thisModuleId_);

and thus the module function does not have to close over its loader.
Capability security regained. The specific syntax of this sort of thing
remains to be hashed out.

     *   *   *   *   *

Mark Miller made some concrete suggestions as well. He stipulated two
"load()" forms, which we name for the purposes of this discussion only.
"loadf" stands for "load function" and is the basic "load()" we have now. To
load a module function, do:

  moduleFunction = loadf('a/b/c');

which will load "a/b/c.js" as before. He also proposed "loadi", which stands
for "load instance" and actually instantiates the module, in addition to
loading its module function. To use it, do:

  moduleInstance = loadi('a/b/c', { x: 3, y: 4 });

The trick with "loadi" is that it has two conveniences: (a) it loads a
module function and instantiates it in one shot; and (b) it desugars to:

  moduleInstance = loadf('a/b/c')({ loadf: loadf.for('a/b/c'), x: 3, y: 4});

In other words, it automatically passes down to the module being loaded a
version of the current loader that is pre-configured to search relative to
the path "a/b/c".

The reason why conveniences (a) and (b) are mixed together is that I may
call any given module function with two different loaders, so providing a
loader is an instantiation time, not a loading time, thing. So say I load
some module function:

  mf = loadf('a/b/c');

I can instantiate this with two different loaders:

  mi_1 = mf({ loadf: theFirstLoader, x: 3, y: 4 });
  mi_2 = mf({ loadf: theSecondLoader, x: 3, y: 4 });

and the object graphs created in mi_1 and mi_2, including the code they are
transitively connected to, may be wildly different because -- well -- they
were instantiated with different loaders.

There is a final wrinkle in this. Note that we implement synchronous
"load()" on top of an async loader by stipulating that (i) the topmost
module loading is always async; and (ii) sync dependencies are declared in
the Caja module record, and are thus prefetched prior to calling the module.
Thus the sync dependencies are already in the loader's cache when the code
is running. Now, what if an instantiating entity switches loaders along the
way, and the loader provided does _not_ have the requested module in the
cache? The answer is => this is a predictable failure mode. An exception is
thrown. This is, after all, a fairly uncommon case.

Cheers,

Ihab

-- 
Ihab A.B. Awad, Palo Alto, CA
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://www.eros-os.org/pipermail/e-lang/attachments/20091119/e094d252/attachment.html 


More information about the e-lang mailing list