-
Notifications
You must be signed in to change notification settings - Fork 12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Top-level await silently reordering evaluation order seems bad #42
Comments
The intent of In terms of evaluation ordering under TLA, as I mentioned in plenary, TLA executions can always be raced by another top-level importer, so I struggle to see how this is a "reordering". From the consumer perspective, adding I can however appreciate that it might not be clear to users that |
With TLA and without For a concrete example, suppose I update the deps, and one of my deps removed a TLA. I
My concern is that it seems bad to bill something that can change when a module toplevel executes as a blind performance optimization. I can live with if the developer has to figure out once, at the point when the developer decides to change an
This seems to be the crux of the disagreement. Why is this more frustrating than silently reordering top-level executions depending on addition/removal of TLA? The latter seems more frustrating.
I hope that some deferred execution happens is clear, since that's the whole point of the proposal. It's that what gets deferred is hard to know and can change when your dependencies changed. |
This seems less about TLA and more about the execution semantics of defer itself in that it represents a new top-level evaluation which can happen at arbitrary positions. It's also the primary feature of the proposal is the tough part here. One way to think of it is that there's an The point being it is an equal top-level importer like any other, and is subject to the determinism of all top-level importers when racing eachother. require(esm) in Node.js is also very semantically similar as well.
Again, I think this is a fact of defer, not its TLA behaviour. If the dependencies change, the error at the call site of the deferred access changes.
Working code is always less frustrating than code that never works. And I think you're overestimating how much dependency code might fail due to top-level initialization ordering changes that are already subject to random execution determinism and hence libraries have already factored this variation in, versus a user's expectation for a performance feature.
My point is that it might not be clear to users that if you do: import defer * as x from './x.js';
// <nothing else> that the above can execute code. That would be a much more compelling argument for why TLA is an issue to me. |
The issue with TLA throwing at runtime is that there is not a way to acknowledge the break, analysis it and then update the code to continue to opt in now satisfied that the TLA change is acceptable. Even with TLA aside there are other changes that apps will be interested in. For example: // app.js
import * as a from "a.js";
import * as b from "b.js";
...
// a.js
import defer * as lib from "lib";
...
// b.js
export const x = 1; All is well, But if someone changes b to: import * as lib from "lib";
export const x = 1;
export function f() {
return lib.f();
} This has de-optimised a's deferred import. Our developer tooling at Bloomberg looks for this pattern and can warn the developer that they have a lazy import that is "shadowed" by an eager one. So they can remedy the situation. This can be a CI check for example. Both this and the introduction of TLA I think are the job for tooling and performance monitoring to catch. Rather than make them immovable barriers. The A design like: performAsyncWork "lib.js";
...later
function f() {
const lib = import.sync("lib.js");
return lib.f();
} Is effectively the mental model but the module being ready for the sync import is dependent on that top level static request to perform the async work. This coupling motivates a design that clarifies the connection by getting the deferred namespace as part of the preparation. performOnlyAsyncWork * as lib from "lib"; deferSyncWork * as lib from "lib"; import defer * as lib from "lib"; |
I'm still not quite understanding why it's acceptable to have TLA silently change the evaluation order.
If you
import defer
a module graph, subgraphs that contain TLA are eagerly evaluated. So, when a dependency deep down in the tree gets (or removes!) a TLA, the set of modules that get their toplevels deferred silently changes. This seems like very surprising behavior to me.One counterargument I've heard is that these dependencies are out of the importer's control, so if, for example, TLA subgraphs caused
import defer
to throw instead of eager evaluation, thenimport defer
would just break. But from my perspective breaking seems more desirable. If you are usingimport defer
to move some set of toplevel evaluations later in time, would you not be interested in when that set of toplevels changes? This is changing observable behavior instead of a behavior-preserving optimization.The text was updated successfully, but these errors were encountered: