Module linking (Was: The global object should not be the "global scope instance object")

Andreas Rossberg rossberg at google.com
Tue Jan 31 04:48:27 PST 2012


On 31 January 2012 00:50, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:
> 1  module A = B.C
> 2  module D { ... }
> 3  module B {
> 4     let x = B.E.x
> 5     import * from A
> 6     export module B {
> 7       export module E = D
> 8       export module C {
> 9         export module D { ... }
>      }
>    }
>  }
>
>
> I added line numbers above for reference.  I think there is a basic bug in
> the above that may just be a typo.  "B" reference in line 1 must refer to
> the module defined in line 3.  Hence "B.C" must refer to an export of "C"
> from that module. However, the only export from that module is line 6 which
> exports "B" so the "B.C reference in line 1 is invalid.  This does require
> any circular analysis and line 5 doesn't impact it.
>
> To get around this  this issue, I looked at assuming that line 6 wasn't
> really intended to be there but that seems to break your example in other
> ways.  What did you really have in mind?

Yes, I'm sorry, the example was messed up indeed. Let me try to fix it:

  module A = B.C.F
  module D { ... }
  module B {
     import * from A
     import x from G
     export module C {
       export module E = D
       export module F {
         export module G = E
         export module D { ... }
       }
    }
  }

It's probably still overcomplicated. It was just intended to show how
entangled things can get. In particular, there is no bottom-up,
left-to-right, or otherwise straightforward order in which you can
derive module interfaces. The dependency graph generally contains
arbitrary cycles, so not even dependency analysis is of much use.
That's why it becomes (more easily expressible as) a constraint
problem.

Full-blown import * adds the additional issue that you may have to
analyse its RHS (here, A aka B.C.F) which refers to an inner scope
(here, F) before you can even tell the extent of an outer scope (here,
B), which makes straightforward variable lookup impossible when
analysing the inner scope. The semantics I played around with actually
rejects such uses of import *, requiring that it only refers to outer
modules. But that may be a pretty severe restriction. (On the other
hand, it's the only restriction that I impose.)

> I agree that * imports complicate matters.  I thought there had been
> discussion at some point about eliminating them, but I'm not yet convinced
> that by themselves they turn this into a constraint solving problems.

Just to clarify: static checking amounts to a constraint solving
problem even without import *, due to the highly cyclic nature of
definitions and uses.

I realize that I might be scaring off people here by talking about
constraint solving, or -- dare! -- type inference. Don't worry, modulo
full-blown import *, these constraints are pretty simple, and easy and
efficient to solve.  That they resemble a well-understood type
inference problem is good news, not bad. The implementation is not
difficult either, my static analysis prototype is ~100 lines for a
core version of modules.

> Instead I think that we need appropriate static restrictions to ensure that it
> can never be such a problem.  If it takes solving to understand module
> compositions then we have created a too power model of module composition.
> If the module linkages aren't pretty damn obvious to both the writer and
> the reader, then we've done something wrong.
>
> I'm actually not a bit fan of using nesting to do module wiring, but I don't
> think it has result in needing constraint solving to understand the wiring.

Well, I don't see much of a way around it, unless you are thinking
about making modules non-recursive. Any other restriction I was able
to come up with would have vastly reduced the utility of some
features, like module aliasing.

In terms of user experience, I don't think it matters much, since I
don't expect users to write programs whose structure is a brain
teaser. Even the bad example above isn't that difficult to untangle
for a human reader, she just has to read back and forth a little.

Reformulating it as a constraint problem is just an elegant way to
avoid doing that back and forth in the algorithm, so that one
efficient pass suffices for the analysis.

Note also that the spec doesn't need to specify it that way. In fact,
it shouldn't. A static semantics should be specified declaratively,
not algorithmically. And declaratively, the story is rather simple.
(Although admittedly, I currently wouldn't know right now how to
formulate that declarative specifications in a style that fits the ES
spec.)

/Andreas


More information about the es-discuss mailing list