Module naming and declarations

Claus Reinke claus.reinke at talk21.com
Fri May 17 07:49:32 PDT 2013


I'm still trying to make sense of the conflicting module naming design
goals and usage consequences.

>> You seem to believe otherwise, but I think you still need to explain
>> how any of the above cases is not sufficiently (or even superiorly)
>> supported by lexical modules + the loader API.
>
> The most important flaw of this is staging. The loader API lets you
> *dynamically* modify the registry, but those changes cannot be
> used by code compiled at the same time as the code that does the
> modification.

If loader/registry manipulation and module declaration happen
in different stages, and we have sub-projects that both provide
and consume common libraries, then we have a staging problem.

This happens when the loader configuration stage cannot refer
to lexical names in the project loading stage.

> As I said above, this is broken. If we don't provide a declarative way 
> to register modules, then they have to be added to the registry *in a 
> different stage* from any code that uses them. This forces you to 
> sequentialize the loading of different packages of your application, 
> which is a non-starter.

Replacing module name declarations with strings that are registered
in-stage works around that problem, at the price of replacing scoped 
declaration with (compilation-time single-)assignment and storing/
referencing all local modules in the same (loader-)global registry.

Lexical module names and string-like module registry names
fulfill different purposes, and trouble comes from trying to make 
one serve the uses of the other: the earlier modules design only
had lexical names, which is awkward wrt configuration; the current
design has registry entries, which is awkward wrt local references.

The way to avoid such awkward mismatches of provided concepts
and use cases, then, seems to require both lexical modules and
registry entries, as separate concepts, each with their own uses.

Since we also need to get external modules from somewhere, this
leaves us with three levels of references:

1. module declarations and module references for import/export
    use lexical naming

    module m { ... }    // local declaration
    import { ... } from m    // local reference

2. registry entries for module registration and reference

    (a) use string-like naming

    module m as "jquery" // non-local/loader registry entry
    module m from "jquery" // non-local/loader registry lookup

    (b) use property-like naming

    module m as Registry.jquery // non-local/loader registry entry
    module m from Registry.jquery // non-local/loader registry lookup

    (c ) use modules of modules

    export { jquery: m } to Registry // non-local/loader registry entry
    import { jquery: m } from Registry // non-local/loader registry lookup

3. external references use urls, here marked via plugin-style prefix,
    to separate from registry references

    module m from "url!<jquery url>" // external resource reference

The main points of registry manipulation are that it happens before
runtime and is single-assignment, so that it can affect the loader that
is currently active. 

I'm not sure that I'd call this declarative (to begin with, it seems 
order-dependent), and string names (2a) do not seem to be necessary, 
either - they just make it easy to embed URLs in names.

String names (2a) make registry entries look like external references,
or rather, they put what looks like external references under control
of the loader. There could be a convention (3) that "url!<url>" refers
to an external reference, via <url>, but -by design- all string-named
module references are configurable. If lexical module names (1) are 
not included, all module references are configurable.

Property names (2b) make registry entries look like lexical references,
the only indication of (load-time) configurability being the "Registry."
prefix. That is even more apparent in the import/export variation (2c).

No matter which of the three variations of (2) is used, the part about
register-a-module is a little odd, and my variations are meant to 
highlight this oddity

    module m as "jquery" // (2a)
    module m as Registry.jquery // (2b)
    export { jquery: m } to Registry // (2c)

Other variations would obscure the oddity, e.g., mixing definition and
registration in a form that suggests (local) naming

    module "jquery" { ... }

To illustrate the oddity a little further: if we consider a project SPa with
sub-projects SP1 and SP2, whose modules need to use some common 
library like JQuery, we end up with two phases for SP:

phase 1: configure and register jquery (versions/locations)
phase 2: load SP1 and SP2, do the SPa things

The proposed spec makes it possible to load configuration script and
sub-projects in one go, because the configuration script modifies the
loader that is used by the sub-projects. Which means that the phases
have to be loaded in this order, and that re-configuration has to be an 
error to preserve single-assignment.

However, this only works because neither SP1 nor SP2 do their own
configuration: without lexical names, there are no local modules, so
the sub-projects would conflict over the common registry name; also,
if the sub-projects weren't open wrt common library references, the 
configuration phase wouldn't affect them.

Now, what if we want to use SPa as a whole, perhaps together with
another sub-project SPb? It seems we have to split SPa into its 
configuration and load phases again, so that

- we can re-configure common library uses between SPa and SPb
- we do not get conflicts wrt configurations in SPa and SPb

One common solution to this problem is to make the open-ness/
import-configurability explicit in the module system, by using
module-level functions (standard example SML). We know how
such systems can be made to work. I'm not sure what the suggested 
use pattern for ES6 modules with module path configuration is.

Claus
 


More information about the es-discuss mailing list