Do Anonymous Exports Solve the Backwards Compatibility Problem?

David Herman dherman at mozilla.com
Wed Dec 19 15:04:20 PST 2012


On Dec 19, 2012, at 11:44 AM, Kevin Smith <khs4473 at gmail.com> wrote:

> But that cowpath was only created because of the problems inherent in a dynamic, variable-copy module system, as I argue here (https://gist.github.com/4337062).  In CommonJS, modules are about variables.  In ES6, modules are about bindings.  The difference is subtle, but makes all the difference.

It took me a while to understand what you were saying, so let me try to explain it for others who may have been confused like me:

<kevin's point>
In CommonJS, since a module is just an object, extracting it with `var` dereferences the *current* value but does not alias the object's property. So the local variable in the client module gets a stale copy of the exported binding, rather than being an alias for that export. By contrast, ES6's `import` provides an alias for a module's binding.

So in Node, if you want to keep a live view of the exports of a module, you should pass the module around as an object and always dereference it. This is a big justification for the "just one value" idiom -- it allows a module to have mutable exports without breaking client code.
</kevin's point>

That's an interesting point, and one that I admit I hadn't thought about. Thing is, I'm really not sure it's the primary justification for the "just one value" idiom. It always seemed to me it was about being able to unify the primary abstraction provided by a module with the module itself. For example, when I pointed out to substack that you could easily do:

    // foo.js
    export function foo() { ... }

    // client.js
    import foo from "foo"

or

    import { foo: myLocalNameForFoo } from "foo"

He complained that it still forces the client to refer to it by a name. IOW, the same complain about the .t thing in ML.

> The Zepto/jQuery example is not typical, but it's fair game.  What's going on there?  Well, jQuery is defining an interface, presumably consisting of a function named "jQuery".  If Zepto wants to provide the same interface, then it should export bindings with the same name: a function named "jQuery".  It's unreasonable to demand that a part of an interface (in this case, the function name), be defined by the *user* of that interface.

Here I disagree; after all, the whole point of having local renaming (the `x: y` destructuring syntax) is to allow the client to provide the name that it wants locally. If a module only has a single binding, then it's fine for it to be anonymous, since there's no other possible binding it could be ambiguous with, and then it's just the same phenomenon. It's just that in this case, it has no default name, so the client *has* to name it.

As I've said, I don't have extremely strong feelings about this issue. In my experience, SML, Ocaml, Haskell, and Racket all do fine without it; the convention is a little wordy but it's just not a big deal. In Rust, we mostly punted -- for a while we had option::t and people didn't like that convention, so now we do option::Option -- but we do have anonymous traits which allow you associate a bunch of operations with a type, which kind of ends up like a module unified with a type.

But at the end of the day, I don't see any way to resolve this debate; I think really what this is about is a question of taste. Neither approach is broken. I think we'll just have to pick an approach and go with it.

Dave



More information about the es-discuss mailing list