Skip to content

Instantly share code, notes, and snippets.

@domenic
Last active December 12, 2015 09:48
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save domenic/4754483 to your computer and use it in GitHub Desktop.
Save domenic/4754483 to your computer and use it in GitHub Desktop.
Single-export vs. multi-import mismatch

This is in response to @BrendanEich's tweet:

that aside, mismatch, e.g., 'export = function...' and 'import f from M' should be an error -- right?

It needs more than 140 characters to reply. In short, the answer is no: this breaks abstraction boundaries. A module consumer should not know which style the module author is using. Indeed, this is a refactoring hazard, disallowing introduction of new behavior for the module being consumed (viz. [[Call]] behavior for export = function () { } or [[Construct]] behavior for export = class { }.)

How I Think It Should Work

Given these modules:

module "glob" {
  function glob() { }
  glob.sync = function () { };
  export = glob;
}

module "jquery" {
  export = class jQuery {
    static ajax() { }
  }
}

The following should not be mismatches:

module "goodConsumer" {
  // Obviously OK, matches syntax on both sides
  import glob from "glob";
  import $ from "jquery";
  
  // But these are also OK; it should use the destructuring assignment algorithm on the single
  // export. (This gets us into trouble with aliasing module import semantics vs. not-aliasing
  // destructuring assignment semantics. The solution, if aliasing must be preserved, is to
  // switch the delimiters to something less deceptive than `{` and `}`, e.g. `<` and `>`.)
  import { sync } from "glob";
  import { ajax } from "jquery";
}

The following should be mismatches, but since the source modules "glob" and "jquery" have opted into single export, there is no way to detect them at compilation time, and they should throw a runtime error:

module "badConsumer" {
  import { asdf } from "glob";
  import { hjkl } from "jquery";
}

If aliasing were abandoned, in order to avoid the weird mismatch between destructuring assignment and importing (see above code comment), then it would even make sense to have an irrefutable variant using the new destructuring assignment syntax:

module "badConsumer" {
  import { ?asdf } from "glob";
  import { ?hjkl } from "jquery";
}

Refactoring Example

As of v0.8, the Q module had no [[Call]] behavior. We supported creating new promises via Q.resolve. That is, our code looked somethinig like this:

module "Q" {
  export function reject() { };
  export function resolve() { };
  export function all() { };
  // etc.
}

module "qConsumer" {
  import { resolve } from "Q";
  
  export function createPromiseFor5() {
    return Q.resolve(5);
  };
}

As of version 0.9 (currently in master), we are switching to Mark Miller's preferred semantics for Q, which is as a callable function that has the same behavior as our current Q.resolve. That is, our code becomes something like this:

module "Q" {
  function Q() { };
  Q.resolve = Q; // backcompat!
  Q.reject = function () { };
  Q.all = function () { };
  
  export = Q;
}

We have tried dilligently to preserve backwards compatability by exporting a top-level resolve property, hoping that every consumer who doesn't have time to update his code would be able to use that. This should be a straightforward "refactoring" in the purest sense of the word, i.e. no external changes should be visible. Only new features were added; the "qConsumer" module above should still work.

But without the above "How I Think It Should Work" section in place, it sounds like this would fail, and our consumers would be broken. Q would be effectively blocked---by its consumers!---from adding new behavior, which is something that should never happen.

In short, the abstraction boundary has been broken, creating a two-way dependency between consumers and producers, both of whom are intimately aware of each others' internals.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment