Open surma opened 3 years ago
As discussed during the plenary last week, one big problem with this is default arguments. They appear outside of any "block". While default arguments have always been evaluated within the scope of the function, it'd be particularly surprising for developers that they cannot reference the outer scope, limiting their use to syntax and built-ins (or other globals expected to be set in the execution environment). In effect the module
prefix creates an isolated scope without stronger syntactic delimitation.
I am sympathetic to that argument. However, I though about it more and I am increasingly wondering how big that problem really is, since — at least in my experience — default values are almost always primitive values, even for more complex function signatures.
module function nearestColor(color, {space = "srgb", numCandidates = 3} = {}) {
/* ... */
}
A function like this would work as expected.
I am also wondering if there really would be developer confusion without a stronger syntactic delimitation. yield
and await
only work in functions that have been defined with a specific keyword. I am aware that this is not a perfect comparison, but I’m mostly thinking out loud.
I think some more real world examples would really help if we want to motivate this. Rather than sending a number to another thread to multiple it by 2.
My main concerns are
What about strict mode inside the body already applying to the signature? Admittedly when the body is changing from sloppy to strict, non simple lists throw, but it’s still an example of something inside the body affecting how the signature is treated.
This seems great to use as an elegant way to share code between contexts. I wonder if the arrow function might be overkill though, where the function declaration form is already sweet enough on its own for the most part?
I like this shorthand a lot and I can see this being used to shortcut small operations done on top of other imports.
The examples on top are good enough for the shorthand, but we can also use it for shadowrealms for code with small dependencies.
const realm = new ShadowRealm();
const result = await realm.importValue(module async () => {
const { doSomething } = await import('framework');
return doSomething(42);
}, 'default');
// instead of
const result = await realm.importValue(module {
export default async () => {
const { doSomething } = await import('framework');
return doSomething(42);
}
}, 'default');
I can say that module blocks might one day be useful to realize promise.there
.
remotePromise.there(module local => {
})
The examples on top are good enough for the shorthand, but we can also use it for shadowrealms for code with small dependencies.
Is this really a representative use case? I'd expect realm imported values to be mostly reusable functions, not simple primitive values. And even if it's getting a primitive result from a scaffolded operation, would imports really be dynamic instead of static?
I think some more real world examples would really help if we want to motivate this.
Same, I'd like to see more real world examples. In my mind, offloading an operation to another thread is not worth it for simple tasks. In that context, making it easier to express trivial tasks as module function
is probably opposite of what we should encourage, where the more verbose syntax captures that cost a little more.
However I can also imagine a complex task already defined in another thread requiring parametrization for some operations (e.g. a filter
or sort
predicate), so a module function
syntax would be a natural way to remove that barrier.
Hmm, this doesn't seem like a big change from the previous version (module function (...) {}
). And I think the previous version even looks better (it has a function
to indicate the visual context) if I have to choose one of those.
After some time has passed, I’m leaning towards not adding the shorthand syntax to the first iteration of the proposal. We can still add it later if the real-world usage of module blocks would benefit in a significant way.
It seems the motivation for this syntax sugar is to simplify this syntax:
.pipe(map(module { export default value => value * 3 }))
to this:
.pipe(map(module value => value * 3))
Which at first glance does not sound like it is worth the effort. However, it sort of makes sense, following the logic in next 3 steps:
.pipe(map(module export default value => value * 3))
.pipe(map(module default value => value * 3))
.pipe(map(module value => value * 3))
But then it still does not add up, as map
operator in RxJs accepts a synchronous function. But module ...
would return a Module
, and to get something out of that module it needs to be spun up in some thread first; and to retrieve something from that thread, that would be an asynchronous operation.
At the October TC39 f2f, I presented a newly added syntax: Module Functions
One core motivation for Module Blocks is providing a language-level primitive that unlocks patterns for parallelism, because JavaScript can’t (easily) adopt the shared memory parallelism model that most other languages utilize.
I did some research into the ergonomics and patterns of other languages and other platforms, and found that many use functions as their fundamental primitive. For example, here’s reactive programming in Swift on iOS and Kotlin on Android:
While JavaScript shouldn’t be adopting other language’s idioms 1:1 due to the lack of shared memory, I want to enable similar patterns in JavaScript. If we use a RxJS-inspired observable syntax, I think it becomes clear that a full module syntax would be quite noisy for this use-case.
To allow JS to implement these patterns in a syntactically lightweight way, I added function short-hand to the proposal that merely acts as syntactic sugar.
Thinking along Observables also made me revise my statement in a previous issue: I am not as confident anymore, that import-less module blocks would be that uncommon.
I feel like this is a very lightweight addition to the proposal that can make a lot of common patterns a lot easier to follow in JS.
Looking on input, so pinging some folks I know have opinions! @Jack-Works @domenic @nicolo-ribaudo @guybedford @leobalter