{"action":"created","issue":{"url":"https://api.github.com/repos/tc39/ecma262/issues/368","repository_url":"https://api.github.com/repos/tc39/ecma262","labels_url":"https://api.github.com/repos/tc39/ecma262/issues/368/labels{/name}","comments_url":"https://api.github.com/repos/tc39/ecma262/issues/368/comments","events_url":"https://api.github.com/repos/tc39/ecma262/issues/368/events","html_url":"https://github.com/tc39/ecma262/issues/368","id":131755923,"number":368,"title":"imperative imports","user":{"login":"dherman","id":307871,"avatar_url":"https://avatars.githubusercontent.com/u/307871?v=3","gravatar_id":"","url":"https://api.github.com/users/dherman","html_url":"https://github.com/dherman","followers_url":"https://api.github.com/users/dherman/followers","following_url":"https://api.github.com/users/dherman/following{/other_user}","gists_url":"https://api.github.com/users/dherman/gists{/gist_id}","starred_url":"https://api.github.com/users/dherman/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dherman/subscriptions","organizations_url":"https://api.github.com/users/dherman/orgs","repos_url":"https://api.github.com/users/dherman/repos","events_url":"https://api.github.com/users/dherman/events{/privacy}","received_events_url":"https://api.github.com/users/dherman/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":3,"created_at":"2016-02-05T21:14:08Z","updated_at":"2016-02-05T22:42:07Z","closed_at":null,"body":"ES2015 specifies a _sequentialized_ evaluation order of modules, where the particular syntactic location of an `import` statement in a module body has no effect on the order of evaluation. Instead, the semantics simply determines a module's list of direct dependencies and recursively calls ModuleEvaluation() on each of them in order *before* evaluating the body. I'll refer to this semantics as **declarative imports**.\r\n\r\nAn alternative semantics would be an _interleaved_ evaluation order. In this semantics, the evaluation order **is** sensitive to the particular syntactic location of an `import` statement. The evaluation semantics would be simply to execute the module body in order, and each ImportStatement would recursively call ModuleEvaluation() on that dependency. I'll refer to this semantics as **imperative imports**.\r\n\r\n\r\n# It's not too late to change\r\n\r\nExisting implementations of module systems in production use today vary between these two semantics. As I understand it, AMD and YUI have declarative imports, CJS And Node have imperative imports, some ES6 transpilers follow the currently specified declarative semantics, and others such as Babel and TypeScript can configurably target AMD or CJS and inherit the semantics of the target system depending on that configuration. Meanwhile, no browsers are yet shipping native support for ES6 modules.\r\n\r\nSo my conclusion is that it's not too late to change this part of the spec.\r\n\r\n\r\n# Impact on top-level await\r\n\r\nOne consideration we would have to work out is what becomes of the semantics of top-level `await`. The only two options I can think of are:\r\n\r\n1. the presence of any dependencies on _asynchronous modules_ (i.e., modules that contain top-level await or that import from other asynchronous modules) automatically switches a module's evaluation order to declarative; or\r\n2. imperative imports from asynchronous modules implicitly `await`.\r\n\r\n(1) is pretty silly and a refactoring hazard to boot. I think (2) is natural and reasonable, but it does have the consequence of being a slightly nonobvious `await` point:\r\n\r\n```js\r\nconsole.log(\"before\");\r\nimport \"some-async-module\"; // implicitly blocks; remainder of module body does not execute in this turn\r\nconsole.log(\"after\");\r\n```\r\n\r\n\r\n# The case for imperative imports\r\n\r\n## Compatibility with Node\r\n\r\nNode is clearly the largest ecosystem, and consequently the most likely one to hit on compatibility problems with declarative imports. This is IMO probably the strongest argument for imperative imports.\r\n\r\n## Connecting side effects to statements\r\n\r\nSince module initialization is effectful, imperative imports arguably give programmers clearer control over those side effects. I don't find this argument quite as compelling, since memoization means you can't actually predict whether the side effects are occurring now or have already occurred some time earlier. You can at least claim that it's more consistent with the familiar precedent of `require()` -- although this is roughly equivalent to the previous argument.\r\n\r\n## Increased expressivity\r\n\r\nIn the presence of cycles, imperative imports are technically more expressive. For example, [two mutually recursive classes](https://gist.github.com/dherman/721762f0cd0be72c3166) can coordinate to initialize their definitions and then add static instances of one another, without requiring a third module to orchestrate the initialization:\r\n\r\n```js\r\n// module A\r\nexport default class A { ... } // create class A\r\nimport B from \"B\";             // SYNCHRONIZATION POINT\r\nA.B_INSTANCE = new B();        // use class B\r\n```\r\n\r\n```js\r\n// module B\r\nexport default class B { ... } // create class B\r\nimport A from \"A\";             // SYNCHRONIZATION POINT\r\nB.A_INSTANCE = new A();        // use class A\r\n```\r\n\r\nThat said, this is an extremely subtle and fragile dependence on the execution order. In practice, most developers do not have a good mental model for the execution order of cycles and are unlikely to want to depend on these kinds of subtleties. So in that sense this argument is somewhat weak. But I suspect this means fewer cyclic dependency graphs will break.\r\n\r\n\r\n# The case for declarative imports\r\n\r\n## Simpler top-level await story\r\n\r\nThe story of top-level await is arguably more straightforward with declarative imports: the evaluation of the entire module body is simply blocked on completion of its dependencies. This doesn't introduce an implicit `await`, whereas imperative imports implicitly await at the point of `import`ing an asynchronous module.\r\n\r\n## Refactoring\r\n\r\nSince declarative imports do not depend on the order of imports, you can reorder them without changing the behavior of your program. This gives programmers more flexibility to group their module bodies the way feels best to them.\r\n\r\n## No harm done\r\n\r\nLarge ecosystems like the Ember community have been using the ES6 semantics for years without trouble, so it does not appear to have been a problem in practice.\r\n\r\n## Simpler spec\r\n\r\nDeclarative evaluation is simpler to spec than imperative evaluation, which requires the evaluation semantics of import statements to coordinate with the rest of the module semantics. I reject this argument entirely -- if @bterlson has to suffer to make JS developers' lives better, so be it! ;P\r\n\r\n\r\n# Conclusion\r\n\r\nIMO, imperative imports come out ahead here: in particular, the compatibility and familiarity from existing module systems is the strongest argument I see. Of the arguments I've enumerated, the only one I can see that I find a bit concerning about imperative imports is the implicit `await`.\r\n\r\nBut I may well have missed arguments, so let's talk!"},"comment":{"url":"https://api.github.com/repos/tc39/ecma262/issues/comments/180606013","html_url":"https://github.com/tc39/ecma262/issues/368#issuecomment-180606013","issue_url":"https://api.github.com/repos/tc39/ecma262/issues/368","id":180606013,"user":{"login":"creationix","id":89353,"avatar_url":"https://avatars.githubusercontent.com/u/89353?v=3","gravatar_id":"","url":"https://api.github.com/users/creationix","html_url":"https://github.com/creationix","followers_url":"https://api.github.com/users/creationix/followers","following_url":"https://api.github.com/users/creationix/following{/other_user}","gists_url":"https://api.github.com/users/creationix/gists{/gist_id}","starred_url":"https://api.github.com/users/creationix/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/creationix/subscriptions","organizations_url":"https://api.github.com/users/creationix/orgs","repos_url":"https://api.github.com/users/creationix/repos","events_url":"https://api.github.com/users/creationix/events{/privacy}","received_events_url":"https://api.github.com/users/creationix/received_events","type":"User","site_admin":false},"created_at":"2016-02-05T22:42:07Z","updated_at":"2016-02-05T22:42:07Z","body":"Also as a bit of anecdote, I had added top-level yield to luvit modules (a lua clone of node.js) by re-implementing the require system in lua.\r\n\r\nAfter several years, only a tiny fraction of users actually took advantage of the edge case you mentioned (namely my lit project).  Eventually I decided to drop the hack to regain the more valuable interop with native lua require.  The change to my code was painful, but quite straightforward https://github.com/luvit/lit/commit/9b7ad7a53fc125675aa149ab8cd845b32d2430e4.\r\n\r\nIf I could go back, I would have never added the ability to yield at the top level."}}