09:00
<shu>
what is the last item scheduled for today?
09:03
<Rob Palmer>
ShadowRealm
09:04
<Rob Palmer>
but it may move slightly if time magically opens up
09:05
<shu>
i should like to be present for shadowrealms but tbqh i don't know if i'll be able to stay awake until then
09:08
<ljharb>
* can someone let me in to the meet? Rob Palmer ryzokuken bterlson
09:09
<Luca Casonato>
bakkot: can you add amazing iterator -> async iterator?
09:19
<ljharb>
bterlson: advance queue?
09:23
<Rob Palmer>
As Michael Saboff said, please add your name, abbreviation and organization to today's meeting notes. Link here: https://github.com/tc39/Reflector/issues/446
09:24
<Rob Palmer>
Please could everyone avoid sharing direct URLs to our notes or meeting URLs in this publicly logged room. These are all linked from the Reflector post so that they are kept private.
09:28
<Michael Ficarra>
already 15 min ahead!
09:37
<Christian Ulbrich>
Wouldn't this be also the first time, that we would have a ~keyword~operator being extended / giving a . ? await is not a normal function but sth. magical like delete?
09:38
<ljharb>
no, we have new.target
09:38
<ljharb>
and super.whatever
09:38
<rbuckton>
and import.meta
09:38
<ljharb>
metaproperties are already A Thing
09:40
<Christian Ulbrich>
super.whatever is used like a magical function and new.target is a magical property. import.meta a magical global.
09:40
<yulia>
speaking of research -- anyone wanna take over running that call?
09:42
<Christian Ulbrich>
I can relate to the idea of hiding Promises, but it would come at the cost of having - IMO - at the cost of another magical thing, that we do not have so far.
09:44
<ljharb>
it could be an awaitAll keyword and it'd be the same
09:44
<yulia>
we have issues with sound in the room?
09:44
<ljharb>
await.all isn't magical, it's just a way to nest keywords under keyords in this case
09:44
<bakkot>
await.all [a, b, c] just doesn't seem that different from await Promise.all([a, b, c]) to me
09:45
<rbuckton>
async/await isn't about hiding promises. It's about taking continuation passing style code and allowing you to write it as linear code so that you can maintain the benefit of existing control flow constructs like continue/break/return/yield.
09:45
<yulia>
what is being highlighted right now is the issue of breaking consistency in a language -- new developers will have a hard time keeping two things in mind
09:45
<ljharb>
await.all [a, b, c] just doesn't seem that different from await Promise.all([a, b, c]) to me
sure, i do the latter all the time, but in my experience devs in fact consider those quite different, and avoid the latter in favor of await a; await b; await c constantly
09:45
<yulia>
I would question teaching async/await before promises for this reason
09:45
<yulia>
i don't think the solution here is syntax
09:46
<Christian Ulbrich>
In other words, is there so far any await-like thing with a .?
09:46
<ljharb>
Christian Ulbrich: no, in that respect it'd be something new, but almost every new syntax feature has that :-)
09:46
<ljharb>
yulia: i totally agree this doesn't fix the education problem. to me it's fixing an ergonomics problem.
09:46
<Alex Vincent>
DevMo has a great example of how to do promises in sequence via Array.prototype.reduce
09:46
<bakkot>
ljharb I definitely see people do await a; await b; await c over await Promise.all but I have generally assumed it was because of thinking serially rather than because of wanting to avoid Promise?
09:46
<yulia>
yulia: i totally agree this doesn't fix the education problem. to me it's fixing an ergonomics problem.
the ergonomics issue is relying on learners to make its poinit
09:46
<bakkot>
"wanting to avoid Promise" seems like a problem we should not be trying to solve for them
09:46
<yulia>
you just did so as well
09:46
<Kris Kowal>
I imagine for await all would take a lot of pressure off of await.all.
09:46
<yulia>
i don't agree that this is a solution
09:47
<ljharb>
ljharb I definitely see people do await a; await b; await c over await Promise.all but I have generally assumed it was because of thinking serially rather than because of wanting to avoid Promise?
my experience is that most of the time they don't need to be serial, and that they didn't want to deal with the ceremony of Promise.all
09:47
<Christian Ulbrich>
Motivate a change to "unconfuse" developers from Promises by adding a totally new syntax, should be well-thought of.
09:47
<shu>
ljharb I definitely see people do await a; await b; await c over await Promise.all but I have generally assumed it was because of thinking serially rather than because of wanting to avoid Promise?
this rings true to me
09:48
<shu>
i reckon beginners are not at a place that would reach for parallel combinators but for a more discoverable way to do so, like syntax, or something
09:48
<shu>
they're just not at a place to reason about things in parallel
09:48
<Christian Ulbrich>
Concerning the hiding motivation, it actually is a problem that have all the time with junior developers, they already do not know, that they are using promises...
09:48
<Kris Kowal>
I agree with @yulia that promises should be taught before async/await. I agree with rbuckton that the latter is not to hide the former.
09:49
<HE Shi-Jun>
if the main problem is await all, should we only add await.all without await.race and others? Or even just introduce a special syntax (eg. await* promises)?
09:49
<shu>
first we must teach one of the First Mistakes -- the existence of the microtask queue
09:49
<Christian Ulbrich>
shu: The whole Playwright API is heavily async...
09:49
<shu>
Christian Ulbrich: sorry what's the context of that comment? i don't know what that means
09:51
<ljharb>
if the main problem is await all, should we only add await.all without await.race and others? Or even just introduce a special syntax (eg. await* promises)?
that would still be better than the status quo, but i think that'd be an unfortunate inconsistency
09:51
<Alex Vincent>
"do nothing" is always an option.
09:51
<HE Shi-Jun>
ljharb: if syntax, there is no inconsistency?
09:52
<ljharb>
the inconsistency would be 3 of the promise combinators lacking syntax, but 1 having it
09:55
<HE Shi-Jun>
I mean, if 80% usage of combinators is just Promsie.all, is it ok to only add syntax for it?
09:58
<ljharb>
should we only add a subset of + - * /, because some of them get more usage than the others?
09:58
<yulia>
i remain adamant that consistency is important for learnability of a language, 20% is a pretty big chunk, but i think we are just pulling numbers from the air right now.
10:00
<Jack Works>
speaking of research -- anyone wanna take over running that call?
Sad to see you cannot attend
10:00
<yulia>
Sad to see you cannot attend
wanna take over?
10:00
<yulia>
you can bug felienne
10:00
<yulia>
or we can go without a research call for a while
10:10
<ljharb>
bterlson: it'd be really helpful to plan my sleep if the hackmd could be updated with an estimated plan for the rest of the day 🙏
10:10
<Jack Works>
they're just not at a place to reason about things in parallel
Sounds true. For beginners they need to be taught to be aware of parallel. But for me, I sometimes drop parallelism because I'm lazy to write Promise.all()
10:11
<Rob Palmer>
ljharb: hackmd is constantly kept as up to date as we can make it - as we approach the end of the meeting it generally becomes more reliable as uncertainty decreases
10:11
<Kris Kowal>
I think Promise.all is the tip of the lazy iceberg. To go from lazy about parallelism to rigorous usually means converting a for loop to a map, or accumulating an array of promises in a for loop.
10:11
<ljharb>
thanks, appreciate whatever you can do!
10:12
<shu>
Jack Works: have you tried to use editor abbreviations?
10:12
<Jack Works>
"do nothing" is always an option.
I'm prepared and think it's ok if the committee dont like this idea
10:12
<shu>
like this seems like an easier problem to solve than through the language
10:13
<Kris Kowal>
A much bolder improvement would be parallel for await loops.
10:15
<Jack Works>
should we only add a subset of + - * /, because some of them get more usage than the others?
OOT: imo some operators (| & %) do not deserve a single character operator because they're not used so often (than || &&)
10:15
<rbuckton>
OOT: imo some operators (| & %) do not deserve a single character operator because they're not used so often (than || &&)
As someone that does a fair amount of bit twiddling, I disagree
10:16
<Kris Kowal>
OOT: imo some operators (| & %) do not deserve a single character operator because they're not used so often (than || &&)
K&R’s Regret. They got to bitwise first.
10:16
<sffc>
I would like 10 minutes of plenary time to discuss an additional question regarding Intl NumberFormat v3. I have added a slide at the end of the deck I used on Tuesday
10:16
<Jack Works>
wanna take over?
I have little knowledge about how to do research... I can join the meeting but I don't think I can take the charge 👀
10:16
<apaprocki>
sffc: have you proposed strings for the eras anywhere?
10:17
<yulia>
i think the only person who really knows what they are doing is felienne
10:19
<shu>
i know how to confirm my priors and ignore counter evidence
10:19
<Jack Works>
Jack Works: have you tried to use editor abbreviations?
Oh
10:20
<Jack Works>
One of the main pain point is when I'm using for...of loop and I want to use await in the loop body
10:20
<yulia>
i know how to confirm my priors and ignore counter evidence
sounds like ✨science✨
10:21
<sffc>
apaprocki: All context and work on this should be available on the proposal repository; e.g. https://github.com/FrankYFTang/proposal-intl-era-monthcode/issues/1 ... my colleague Manish has a proposal that has been presented to CLDR. There is still room to evolve before they are written down. Would like your feedback if interested!
10:21
<Jack Works>
One of the main pain point is when I'm using for...of loop and I want to use await in the loop body
Does anyone have the same feeling about this?
10:22
<rbuckton>
slides aren't showing on meet
10:22
<yulia>
blank screen
10:22
<HE Shi-Jun>
I can't see the slide
10:26
<Jack Works>
* you don't need to understand what membrane is to know the motivation of this proposal
10:26
<Jack Works>
TLDR: we have tons of revocable Proxies and we need to revoke them in an ergonomic way.
10:28
<rkirsling>
I mean you kind of do if you've never encountered proxy revocation before (I merely know that it's a thing that exists that I've not needed to investigate to any depth)
10:30
<Jack Works>

today:

for (const p of proxiesToRevoke) revokerMap.get(p)()

You need to store tons of revoker functions and call them one by one

10:31
<rbuckton>

I'm curious if DisposableStack can help here, either:

const { proxy, revoke } = Proxy.revocable(...);
stack.defer(revoke);
return proxy;

// later

stack.dispose(); // mass revocation

Or make the return value of Proxy.revocable itself a disposable.

10:32
<Jack Works>
Cannot help.
10:33
<Jack Works>
Proxy does not have a prototype to store @@disposable
10:33
<rbuckton>
I'm not sure how I see how
10:34
<ljharb>
the confusing name of defer aside, that seems like it'd work
10:34
<Christian Ulbrich>
Is the memory argument really so strong?
10:35
<Michael Ficarra>
I don't think we need to determine whether DisposableStack would be sufficient today
10:35
<Michael Ficarra>
this can reach stage 1 and later we don't advance to stage 2 because we realise DisposableStack is a fine solution
10:36
Christian Ulbrich
has never revoked a proxy, why would I? I want all of them to go through my proxy :)
10:37
<HE Shi-Jun>
Proxy does not have a prototype to store @@disposable
DisposableStack defer still could work
10:37
<rbuckton>
And I meant that its not the proxy that is disposable, but the { proxy, revoke } object
10:38
<HE Shi-Jun>
I think DisposableStack could work, but the proposed api seems much easy to use.
10:39
<Christian Ulbrich>
I also had the https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal in mind...
10:40
<HE Shi-Jun>
yeah, cross realm seems a requirement of this proposal?
10:41
<Mathieu Hofman>
I think wrapped revokers could also be optimized in theory, but would any engine realistically perform all these optimizations?
10:42
<Christian Ulbrich>
Proxies now have some heavy uses... they are used by recent Vue3 and libs implementing immutability.
10:42
<Christian Ulbrich>
I still wonder though, whether they are actually revoking their Proxies...
10:42
<HE Shi-Jun>
Not sure whether Vue3/Mobx use revoke...
10:43
<rkirsling>
hard to disagree with shu here as my point above was that I've never actually used a Proxy, let alone thought about revoking one
10:43
<Mathieu Hofman>
you may not have used one directly, but lots of libraries / frameworks do leverage them under the hood
10:43
<rkirsling>
(not that I'm objecting to anything :P my point above was that without membranes, there isn't an obvious context of usage)
10:44
<Jack Works>
yeah, cross realm seems a requirement of this proposal?
don't know maybe we need ask caridy
10:44
<Mathieu Hofman>
I would like to see usage numbers from web browsers (which do not cover the usages in non browser environments)
10:44
<Jack Works>
And I meant that its not the proxy that is disposable, but the { proxy, revoke } object
oh that works.
10:45
<Christian Ulbrich>
Membranes are just a mental model around Proxies. There are many use cases for Proxies, it does not matter, whether they are actually membranes.
10:47
<shu>
Vue is proxying objects to implement immutability?
10:47
<shu>
they expect this to be performant?
10:48
<Christian Ulbrich>
@shu They use it to implement reactivity.
10:48
<HE Shi-Jun>
Vue is proxying objects to implement immutability?
Not immutablitity, but use proxy for reactivity
10:49
<Christian Ulbrich>
I have skimmed through the source, looks like they never revoke them.
10:49
<shu>
interesting, i see
10:49
<Jack Works>
const disposable = new DisposableStack()
function createProxy() {
    const {proxy, revoke} = Proxy.revocable(...)
    disposable.defer(revoke)
    return proxy
    // at this point, revoke only hold by the DisposableStack.
    // engine can GC `revoke` and knowing the Proxy is being revoked after the DisposableStack disposes
}
10:51
<rkirsling>
so do we have a non-theoretical go-to use case of revocation then?
10:52
<Christian Ulbrich>
rkirsling: So far not, because at least Vue, does not seem to use revocation
10:52
<Mathieu Hofman>
Jack Works: according to the definition of liveness, clearing out the proxy target is not necessary, as the target's identity would be unobservable to the program through the proxy already
10:52
<rbuckton>
const disposable = new DisposableStack()
function createProxy() {
    const {proxy, revoke} = Proxy.revocable(...)
    disposable.defer(revoke)
    return proxy
    // at this point, revoke only hold by the DisposableStack.
    // engine can GC `revoke` and knowing the Proxy is being revoked after the DisposableStack disposes
}
I mean, something like new Proxy(target, handlers, { revocationStack: disposables }) might be feasible, but prefer this approach in general
10:53
<Jack Works>
Jack Works: according to the definition of liveness, clearing out the proxy target is not necessary, as the target's identity would be unobservable to the program through the proxy already
the [[ProxyHandler]] might get access to [[ProxyTarget]] when the internal slot is called right
10:54
<Mathieu Hofman>
so do we have a non-theoretical go-to use case of revocation then?
membranes are not theoretical
10:55
<Mathieu Hofman>
the [[ProxyHandler]] might get access to [[ProxyTarget]] when the internal slot is called right
but if the proxy is revoked, the proxy handler would never be invoked with the target anymore
10:57
<Mathieu Hofman>
An editor note might suffice to make it clear that once revoked, the proxy target's identity will no longer be observable by the program through the handler / proxy
10:57
<Luca Casonato>
Thanks bterlson!! 👏
10:59
<Rob Palmer>
We will resume at 13:00 (62mins time)
10:59
<Christian Ulbrich>
I assume that SalesForce' whole LightningLocker stuff being based on Proxies, they are also using revocation
10:59
<bakkot>
if anyone here is on the typescript team and needs something to do over lunch I have a small QoL PR https://github.com/microsoft/TypeScript/pull/51457
11:00
<Michael Ficarra>
I'm realising now that I have a LOT of tests to write for iterator helpers in test262...
11:00
<Michael Ficarra>
btw, link to the thread asking for a TG3 chair that I mentioned: https://github.com/tc39/Reflector/issues/450
11:00
<bakkot>
yeah set methods will be fun too
11:01
<Michael Ficarra>
🙏 hopefully a helpful community member will just do it before I get around to it
11:08
<ljharb>
i'm going to sleep because i'm sick and exhausted, but for Resource Management, i still think https://github.com/tc39/proposal-explicit-resource-management/issues/102 is not resolved, and i think any stage 3 for it needs to be conditional on coming to consensus on that. i hope my absence won't mean my concerns are overlooked (assuming that it otherwise would get stage 3, ofc)
11:09
<shu>
ljharb: is there a summary of your concern?
11:10
<shu>
or can ron characterize it accurately?
11:13
<rbuckton>
IIRC, it's in regards to argument ordering of the adopt method, and thus it's existence entirely
11:15
<rbuckton>
I can add a note to the slides to call this out
11:17
<shu>
great, thanks
11:21
<Alex Vincent>
I missed a key point in my presentation: when we revoke a proxy, the underlying slots are nullified, but the proxy itself remains part of the object graph it lives in. You can't do anything with the proxy, but it's still there because (probably) something else refers to it. The revocation of an object graph, and of proxies pointing to it, means the original objects in that object graph can be gc'd. The proxies themselves are still reachable
11:57
<Alex Vincent>
ljharb: Rob Palmer per the proposals index, I have transferred my GitHub repo to tc39-transfer.
12:03
<Mathieu Hofman>
Markm is still having trouble joining
12:04
<Rob Palmer>
tell him to try again
12:04
<Rob Palmer>
I think I have admin rights to let people in now
12:04
<Mathieu Hofman>
He did
12:05
<Mathieu Hofman>
Thanks!
12:06
<Rob Palmer>
It wasn't me - I suspect Yulia has done it
12:06
<yulia>
nope -- it was ujjwal
12:06
<yulia>
i don't have rights either
12:06
<yulia>
from the shadows
12:07
<ryzokuken>
Permissions fixed
12:08
<Mathieu Hofman>
I missed a key point in my presentation: when we revoke a proxy, the underlying slots are nullified, but the proxy itself remains part of the object graph it lives in. You can't do anything with the proxy, but it's still there because (probably) something else refers to it. The revocation of an object graph, and of proxies pointing to it, means the original objects in that object graph can be gc'd. The proxies themselves are still reachable
As I mentioned above, explicitly emptying the slots is not actually necessary to make the targets eligible for collection once the proxy is revoked
12:21
<yulia>
dumb question about this -- this would still be usable with streams in spite of the lack of async or no?
12:22
<bakkot>
yulia: basically no
12:22
<bakkot>
if the dispose method is async you need the async syntax, which is not in this version of the proposal
12:22
<yulia>
what are current use cases that would be directly impacted here?
12:22
<bakkot>
and stream.cancel is async
12:24
<bakkot>
https://github.com/tc39/proposal-explicit-resource-management/#relation-to-dom-apis has a list
12:24
<yulia>
I'm aware of the the atomic locking/unlocking behavior, but i think this is also in support of the shared structs proposal?
12:24
<bakkot>
about half of which is sync stuff
12:24
<yulia>
ah great thanks
12:24
<bakkot>
also I really like https://github.com/whatwg/html/issues/8557#issuecomment-1331448189
12:25
<yulia>
thanks, that is useful
12:32
<Ashley Claymore>
also I really like https://github.com/whatwg/html/issues/8557#issuecomment-1331448189

adding in do-expressions to this would be nice, for ensuring the cancelation happens asap.

const pages = do {
  using controller = new AbortController.AutoAbort();
  await Promise.all(urls.map(url => fetch(url, { signal: controller.signal }));
};
... // more logic and awaits follow
12:32
<bakkot>
Ashley Claymore: that doesn't actually end up making any difference
12:33
<Ashley Claymore>
ah yes, Im thinking of Promise.race
12:33
<Ashley Claymore>
where it would
12:33
<bakkot>
ah, yeah
12:47
<Michael Ficarra>
Rob Palmer: is VERY quiet
12:57
<bakkot>
I should say also that I do not at all agree with markm's opinion that { using async x = y } causing an await at block exit is a problem
12:57
<bakkot>
I would much prefer to just use that syntax
12:59
<bakkot>
it's not like it's super nested - the using statement is necessarily at the same level as the block where the await will happen; even if it's in the middle of the block, being at the top level of the block means it is inherently not that obscure
13:00
<Kris Kowal>
I tend to agree. Maybe I can help convince markm.
13:02
<shu>
+1 the partial class initialization thing is a pretty good motivation imo
13:08
<Mathieu Hofman>
bakkot: in that short example maybe, but 1) currently all interleaving points within an async function are marked with await, and 2) with a block populated with other statements, the using async would not be as visible
13:10
<bakkot>
I really don't think it would be hard to see the using async, and I consider it to be a sufficient marker of the interleaving point
13:10
<Mathieu Hofman>
we have found nested async interleaving to be a footgun today, and we're considering any further unclear interleaving as very undesirable
13:11
<bakkot>
I consider making using async harder to use very undesirable
13:11
<Mathieu Hofman>
async is not a marker of interleaving. await is
13:11
<shu>
wait i don't understand why "async" isn't a clear indication of an interleaving point?
13:12
<shu>
well it's a marker that the thing contains interleaving points?
13:12
<Mathieu Hofman>
async is a declaration that the thing has asynchronous behavior (returns a promise)
13:12
<shu>
yes, you're declaring a thing that can contain interleaving points
13:12
<sffc>
Congratulations rbuckton on Stage 3! I'm looking forward to using the proposal in the wild 😀
13:12
<Mathieu Hofman>
no you're declaring it returns a promise
13:12
<Mathieu Hofman>
await is the interleaving
13:13
<eemeli>
rbuckton: I would still appreciate seeing what your PluginHost example would look like if DisposableStack was not available.
13:13
<caridy>
trying to join the call! can someone let me in?
13:13
<bakkot>
Mathieu Hofman: I just don't think the "it has to be exactly the await keyword, and not the async keyword" is a rule which is important
13:13
<bakkot>
who is that rule for?
13:14
<Mathieu Hofman>
for people reading / auditing a piece of code
13:14
<bakkot>
right but like
13:14
<bakkot>
why is async not sufficient for those people
13:14
<shu>
more specifically why is using async insufficient
13:14
<Mathieu Hofman>
because that's what async means today?
13:14
<shu>
treat it as a lexeme
13:14
<shu>
people are smart and adaptable
13:15
<rbuckton>
wait i don't understand why "async" isn't a clear indication of an interleaving point?
It's something erights wants to make very clear in the language, that await and yield are the only way to indicate interleaving points. async does not, since you must await the result of the async function to observe the result.
13:15
<bakkot>
yeah, what shu said
13:15
<bakkot>
right and I'm disagreeing with erights
13:15
<Mathieu Hofman>
people are smart and adaptable
you put more trust in people than I do
13:15
<bakkot>
I think using async being a new way to indicate an interleaving point is not a problem
13:15
<bakkot>
it's fine
13:15
<shu>
like this strikes me as "we must ship my mental model or we ship nothing", which doesn't seem great?
13:15
<caridy>
Rob Palmer: can you help? trying to join the call.
13:16
<Mathieu Hofman>
I don't see why we're so willing to throw away the consistency of the language we have today
13:16
<caridy>
thanks
13:16
<Rob Palmer>
done
13:16
<bakkot>
because it's not an important consistency, especially when weighed against the cost of only being able to put using async inside if statements
13:16
<bakkot>
the second thing is much, much more important
13:17
<rbuckton>
right and I'm disagreeing with erights

The relevant discussion can be found in https://github.com/tc39/proposal-explicit-resource-management/issues/101. This argument has been explicitly called out here:

13:17
<shu>
in general there are many surface consistency things we ought to trade off against other things
13:17
<shu>
argument order and coercion order and so forth is often another one
13:17
<Mathieu Hofman>
we consider await in conditional statements to be a footgun, so that doesn't have much weight in our opinion
13:17
<bakkot>
well
13:17
<bakkot>
that's...
13:17
<bakkot>
uh.
13:17
<bakkot>
not an opinion I agree with.
13:18
<shu>
well my response to that is, sorry you don't get to ship an implicit linter in the language
13:18
<bakkot>
you are welcome to not put await in conditional statements but I really do not approve of you designing the language to stop me from doing it
13:18
<shu>
yes
13:18
<rbuckton>
I don't understand that position either. await in a conditional doesn't "release zalgo", so such a constraint seems confusing.
13:18
<Mathieu Hofman>
right, I think we fundamentally disagree with the ability to reason about interleaving points. I don't see why an explicit block marker is so onerous
13:19
<Mathieu Hofman>
I don't understand that position either. await in a conditional doesn't "release zalgo", so such a constraint seems confusing.
it actually does because of the current unsafety of PromiseResolve
13:19
<rbuckton>
well my response to that is, sorry you don't get to ship an implicit linter in the language
?? would like to disagree with you. I was not in favor of requiring parens to disambiguate with || and && given clear precedence rules, but here we are.
13:20
<rbuckton>
No, "releasing zalgo" is writing an API such that consumers cannot safely reason over whether that API will execute synchronously or asynchronously.
13:21
<rbuckton>
If the function is async, it will always complete in a later turn, but will start executing in this turn.
13:21
<bakkot>
Mathieu Hofman I think that most people have not found await in conditional statements to actually be a problem. so I think if you are reasoning about your code in a way where it is a problem, the appropriate way for you to enforce that is by writing your own linting rules, which forbid both await in conditional statements and using async in blocks, and let the rest of us have both of those things
13:23
<Mathieu Hofman>
anyway, I think we deviated a little, and the conditional thing is not that relevant to this discussion. async using is a declaration that the value has an async behavior. It does nothing to indicate that there will later, at the end of the current scope, be an interleaving point, which are today all marked with an explicit await keyword
13:23
<bakkot>
it's a new piece of syntax, and you have to learn what it means
13:23
<Mathieu Hofman>
Mathieu Hofman I think that most people have not found await in conditional statements to actually be a problem. so I think if you are reasoning about your code in a way where it is a problem, the appropriate way for you to enforce that is by writing your own linting rules, which forbid both await in conditional statements and using async in blocks, and let the rest of us have both of those things
we have
13:23
<bakkot>
I really don't think it's that hard to learn
13:24
<bakkot>
Mathieu Hofman: I accept that you have, but stand by my statement, and my position that the appropriate resolution for you is to have lint rules which enforce the stronger invariants that you apparently need; most people, in my experience, do not need them, so we should not bake them into the language.
13:26
<Mathieu Hofman>
again I'm simply asking for the language to stay consistent with itself and mark interleaving points at the location of interleaving
13:26
<bakkot>
and I'm saying I don't think that consistency is worth the cost of making using async harder to use.
13:26
<bakkot>
given that it is, in my opinion, really not that hard to learn what using async does.
13:27
<Mathieu Hofman>
what's insane to me is that adding a async using statement to a plain block introduces an interleaving point at a distance
13:27
<rbuckton>
Mathieu Hofman: I accept that you have, but stand by my statement, and my position that the appropriate resolution for you is to have lint rules which enforce the stronger invariants that you apparently need; most people, in my experience, do not need them, so we should not bake them into the language.
My first response years ago to erights 's concern about explicit await was to use linter to require something like // await using at the end of a block, but that didn't seem to be sufficient at the time.
13:27
<Mathieu Hofman>
it's a refactor hazard
13:27
<bakkot>
it does not seem like any more of a refactoring hazard than the sync call to Symbol.dispose() is
13:27
<bakkot>
or at least not very much more
13:28
<shu>
let's take a step back, what is the thing that you are doing that makes you care so much about interleave points?
13:29
<Mathieu Hofman>
being able to reason when your synchronous execution has been interrupted, and thus your state may have gotten mutated.
13:30
<shu>
why is that not a concern with sync dispose
13:30
<rbuckton>
Any time you run code you run the risk of side effects. +value can have side effects. An implicit await would have side effects, but that's far easier to run into than a side-effecty +value or the like.
If you're writing code where an implicit async interleaving point could be a problem (i.e., potential state mutations in closed-over code), you have the same problem with any await and would likely need to use some type of async coordination primitive to synchronize access.
13:31
<shu>
like i agree this is a problem, which is why i added "can call user code", but that's not specific to async
13:31
<rbuckton>
One point in the favor of the explicit marker is that its harder to recognize you might need to coordinate access if the interleave point is implicit.
13:31
<Mathieu Hofman>
because you control the resource and what it does. You do not control what other promise jobs may have done if suspended
13:31
<shu>
"you control the resource" is in practice, not true
13:32
<Jack Works>
some thought for the current problem:
13:32
<rbuckton>
I attempted to make the point that a declaration like async using x = ... would be sufficient to indicate such coordination might be necessary.
13:32
<Jack Works>
worker.addModule(module {}, import.meta.url)
13:33
<Mathieu Hofman>
if your model allows you to trust all the objects you invoke synchronously, you don't have to worry about synchronous re-entrancy
13:35
<bakkot>
I think the subset of programs in which people are sufficiently careful that they are able to make and maintain an accurate model which allows them to trust all the objects they invoke synchronously, but where the code is being read so quickly that using async does not suffice as a marker of an interleaving point, is quite small
13:35
<shu>
i daresay that subset of programs is exceedingly small
13:36
<shu>
Mathieu Hofman: ISTM your position is predicated on a very opinionated environment that does not generalize, and thus should not be baked into the language
13:40
<Bradford Smith>
Could someone let me back into the meeting? (My internet cut out, so I got dropped.)
13:41
<Bradford Smith>
thx
13:45
<Rob Palmer>
a heads up: we are overrunning in this session. therefore it is possible the meeting may end 15 minutes late in order to give shadow realms the full 60mins
13:46
<danielrosenwasser>
?? would like to disagree with you. I was not in favor of requiring parens to disambiguate with || and && given clear precedence rules, but here we are.
(btw ~3 years in since stage 4 and it turns out nobody cares)
13:47
<shu>
hm that's a good data point
13:48
<rbuckton>
I get frustrated by it every now and then.
13:48
<rbuckton>
So the number is non-zero, even if close to zero.
13:51
<danielrosenwasser>
Let it be known that Ron runs into it :D
13:55
<snek>
did iterator helpers get stage 3?
13:56
<bakkot>
snek: yes it did!
13:56
<Ashley Claymore>
https://twitter.com/robpalmer2/status/1598248214750191616?s=20&t=6pFlDUfZtj5hXI1s5l9ohw
13:56
<snek>
hypeeee
13:56
<Michael Ficarra>
snek: https://mastodon.social/@robpalmer/109437830520093382
13:57
<snek>
ooo now which one do I click on
14:00
<Luca Casonato>
Justin Ridgewell: I want to push back on the statement that module expressions are not useful without module declarations. I would argue that module expressions have very strong motivation, separate from motivation for module declaration.
14:01
<rbuckton>
Motivations aside, there is so much shared between them its difficult to talk about them independently.
14:01
<Luca Casonato>
Motivations aside, there is so much shared between them its difficult to talk about them independently.
I think it is reasonable to talk about expressions without declarations, but not the other way around.
14:01
<caridy>
the fact that the expressions cannot be used to import from, is sufficient for me to keep them independently.
14:02
<Luca Casonato>
I think it is reasonable to talk about expressions without declarations, but not the other way around.
Reason being that there is a future where expressions get shipped, but not module declarations.
14:02
<Luca Casonato>
One can exist and be useful without the other
14:04
<Robert Pamely>
Are nested modules scoped to within the parent module?
14:04
<Luca Casonato>
Are nested modules scoped to within the parent module?
Yes
14:04
<Luca Casonato>
Unless you explicitly export them
14:05
<Jack Works>
the real reason those proposals are separated IMO because they're initially separated presented to the committee (and back to days they're stage 0, they don't looks similar too much)
14:05
<Robert Pamely>
Are modules independently cachable in browsers? If Site A serves lodash, can Site B use that?
14:06
<Jack Works>
Are modules independently cachable in browsers? If Site A serves lodash, can Site B use that?
nowadays I believe answer is not
14:06
<Luca Casonato>

Ie:

module foo {
  module bar {}
  export module baz {}
}

import bar; // Linking error
import baz; // Linking error

import { baz } from foo;
import baz; // Success!
14:06
<Jack Works>
I've heard browsers are shipping cross-site cache isolation to prevent tracking
14:07
<ryzokuken>
besides, practically speaking, due to the widespread use of bundlers it's not very likely
14:07
<Jack Works>
everyone bundles their own React 🤔
14:07
<Robert Pamely>
besides, practically speaking, due to the widespread use of bundlers it's not very likely
Right, but with module declarations you can now cache module based on some other identifier than URL
14:07
<Robert Pamely>
so bundled modules could be cached
14:08
<Robert Pamely>
security concerns aside
14:09
<Jack Works>
so bundled modules could be cached
I strongly against to serialize a ModuleSource and store it on the disk for security reasons. 🆗 post message to Worker ⛔ store it in indexedDB and read it in the future
14:10
<rbuckton>
if we allowed over the wire or on disk serialization of module expressions we'd need a formal binary AST
14:11
<Jack Works>
if we allowed over the wire or on disk serialization of module expressions we'd need a formal binary AST
indexedDB does not show developers how data is stored so that's pure implementation detail.
14:11
<Robert Pamely>
Why is it any different than caching the react source from a CDN, if you cache the react source from a bundled site?
14:11
<Robert Pamely>
(I'm thinking out loud here)
14:13
<Kris Kowal>
I’m reasonably certain that module declarations would break new Module((module {}).source), which is key to user composition of module graphs.
14:13
<ryzokuken>
Right, but with module declarations you can now cache module based on some other identifier than URL
yes! I wonder if this has interesting implications for, say, ad-blocking.
14:14
<bakkot>
Why is it any different than caching the react source from a CDN, if you cache the react source from a bundled site?
there is no longer caching from CDNs https://github.com/whatwg/fetch/issues/904
14:14
<danielrosenwasser>

I'm not huge on needing a separate namespace per module (or bit per value) to track which exports are module exports that can be imported from vs. not

This blog post comes to mind https://gbracha.blogspot.com/2014/09/a-domain-of-shadows.html

14:16
<Kris Kowal>
Module declarations/expressions won’t compose because a module graph that consists of declarations coming from different module files cannot be consolidated into a single module handler, for the purposes of resolving stringly named module specifiers on their various referrers.
14:16
<rbuckton>
Curious about static importability and portability of import module foo from ... and module bar { import foo; }. Is that valid?
14:17
<Robert Pamely>
If I'm reading this correctly, the concern was that CDN sources were manipulated to add tracking code in them that got loaded into every site that uses them?
14:17
<leobalter>
aren't module declarations top level only?
14:17
<caridy>
Slides: https://docs.google.com/presentation/d/183cYPeUjhzVRuZ0O-gwXqp6KtFYxxhgYodbr7XNo_9o/edit
14:18
<rbuckton>

And what about:

const x = module { }
const y = module { import x; }

There is so much inconsistency with the "two worlds" approach that breaks the decl/expr equivalence of function and class

14:18
<nicolo-ribaudo>
aren't module declarations top level only?

The proposal as it is now allows you to do something like this:

{
  module x {}
  await import(module { import x });
}
14:19
<leobalter>
thanks for the info
14:19
<rbuckton>
That's specific to just that case, not a general solution.
14:19
<Kris Kowal>
My other concern is that differentiating lexical and stringy module specifiers will be used to distinguish which modules should be serialized from which should be resolved from a remote worker’s import hooks, and will have ramifications on whether instances are shared. That’ll herd the module ecosystem in a weird direction, where portable libraries will generally have to export a module expression/declaration in order to give the user the choice of whether to link lazily or eagerly.
14:19
<nicolo-ribaudo>

And what about:

const x = module { }
const y = module { import x; }

There is so much inconsistency with the "two worlds" approach that breaks the decl/expr equivalence of function and class

What would you expect to happen here if instead of const x you had a let x, that is later reassigned?
14:19
<rbuckton>
You could be importing a module dynamically from another module that exports it, but can't reference it in another module expression.
14:19
<bakkot>
Robert Pamely: no, the concern is, my site can tell you've visited another site by timing whether loading a resource which is also loaded by that site is cache-hit or not
14:19
<bakkot>
https://github.com/shivanigithub/http-cache-partitioning has a better explainer I think
14:19
<Luca Casonato>
I think it may be a reasonable to investigate using a seperate identifier namespace to clarify the difference between "regular values" and "module values". For example module $foo {}; import $foo;
14:20
<Jack Works>
I think it may be a reasonable to investigate using a seperate identifier namespace to clarify the difference between "regular values" and "module values". For example module $foo {}; import $foo;
but module declarations should be able to import($foo)
14:20
<Jack Works>
so there is no need to separate a new namespace
14:20
<nicolo-ribaudo>
Curious about static importability and portability of import module foo from ... and module bar { import foo; }. Is that valid?
There is no spec text for this yet, but if import reflection reifies to a Module object it can be easily made to work.
14:21
<Jack Works>
we can think of, some values are just available eariler, before the execute
14:21
<rbuckton>
// foo.js
export module A { }

// bar.js
export function maybeDoAThing() {
  const A = await import("./foo.js").A;
  const B = module { import A };
  new Worker(B).postMessage(...);
}
14:21
<Luca Casonato>
we can think of, some values are just available eariler, before the execute
this is difficult to explain to users though. using a separate namespace would make this clear
14:22
<Jack Works>
// foo.js
export module A { }

// bar.js
export function maybeDoAThing() {
  const A = await import("./foo.js").A;
  const B = module { import A };
  new Worker(B).postMessage(...);
}
I believe this is a link error.
14:22
<Luca Casonato>

it's difficult to explain why this is the case:

const a = module {};
module b {};
import a; // link error
import b; // fine
14:22
<rbuckton>
My point is that the "2nd world" approach makes module decls and module expressions inconsistent.
14:22
<rbuckton>
That's a refactoring hazard.
14:23
<leobalter>
Module expressions and declarations are among the most important changes coming to ECMAScript that positively affects web developers and code bundling in order to encourage a better usage of ES modules. I hope TC39 can dedicate more time for those topics in order to advance them without taking years.
14:23
<Jack Works>

it's difficult to explain why this is the case:

const a = module {};
module b {};
import a; // link error
import b; // fine
so maybe we can also accept const ident = module {}. no more complex
14:23
<nicolo-ribaudo>

Thanks for the example, the only way it could work with this proposal is something like this (!!! semantics are different, but the intention is the same):

// foo.js
export module A { }

// bar.js
export function maybeDoAThing() {
  const B = module { import { A } from "./foo.js"; import A };
  new Worker(B).postMessage(...);
}

It's worth investigating when modules should "capture" available modules from the other scope, but it would still only allow capturing other modules and not generic values.

14:23
<Luca Casonato>
so maybe we can also accept const ident = module {}. no more complex
we can't hoist const bindings though. module declarations only work statically, because they are hoisted
14:24
<rbuckton>
I think if we have two things called module where there is a declaration and an expression version, then what they contain and how they handle scoping should be consistent.
14:25
<Mathieu Hofman>
shu: currently the message and name access is specced to be non-observable, which mimics FF, but if there's an argument for otherwise, we could spec to always perform a Get
14:25
<nicolo-ribaudo>
I think if we have two things called module where there is a declaration and an expression version, then what they contain and how they handle scoping should be consistent.
Well, your examples are about scoping of module declarations and const declarations
14:25
<Jack Works>
// foo.js
export module A { }

// bar.js
export function maybeDoAThing() {
  const A = await import("./foo.js").A;
  const B = module { import A };
  new Worker(B).postMessage(...);
}

Some thought:

Link of module B in your example can be deferred to when maybeDoAThing is called. When the expression is evaluated, it checks if A is a Module and if it is a constant binding, if not, it failed to evaluate.

14:27
<rbuckton>
Well, your examples are about scoping of module declarations and const declarations
Yes, because that could be a real use case. If you can dynamically import a module expression, then you can do so conditionally.
14:27
<Michael Ficarra>
uhhh censoring error stacks is already a proposal
14:30
<ryzokuken>
bakkot: user land -> userland
14:31
<rbuckton>

Some thought:

Link of module B in your example can be deferred to when maybeDoAThing is called. When the expression is evaluated, it checks if A is a Module and if it is a constant binding, if not, it failed to evaluate.

Perhaps? This goes to my point about closures. You might, for example need to indicate if a given binding/value is either transparently serializable, replaceable (such as globals), or banned (i.e., non-serializable, non-global).
14:34
<rbuckton>

Accessing a serializable or replaceable binding in a module would be fine, but other bindings would not. There's a circularity issue there though:

const a = module { export function f() { return import(b); } };
const b = module { export function g() { return import(a); } };

This seems like a perfectly reasonable case, but you can't eagerly serialize b because it's not defined. You'd have to defer such serialization until it is needed, so you would again have to capture all bindings.

14:36
<danielrosenwasser>
a and b aren't capturable in that example though, right?
14:37
<rbuckton>
a and b aren't capturable in that example though, right?
My contention is that they should be.
14:38
<Jack Works>

Accessing a serializable or replaceable binding in a module would be fine, but other bindings would not. There's a circularity issue there though:

const a = module { export function f() { return import(b); } };
const b = module { export function g() { return import(a); } };

This seems like a perfectly reasonable case, but you can't eagerly serialize b because it's not defined. You'd have to defer such serialization until it is needed, so you would again have to capture all bindings.

I agree this becomes messy... b and a will be looked up in the global object of it's evaluator instead of links to the module
14:38
<rbuckton>
It would be extremely inconsistent if you cannot model with module expressions the same thing you can model with module declarations. This seems like a major stumbling block for adopters.
14:38
<danielrosenwasser>

oh, because they would be valid if it was written as

module a { export function f() { return import(b); } };
module b { export function g() { return import(a); } };
14:38
<apaprocki>
Justin Ridgewell: they don't want if (runningInShadowRealm)
14:38
<danielrosenwasser>
wait...is that true though?
14:39
<rbuckton>
If it isn't then we don't have the same kind of decl/expr parity that function and class have.
14:39
<nicolo-ribaudo>

oh, because they would be valid if it was written as

module a { export function f() { return import(b); } };
module b { export function g() { return import(a); } };
Yes, this would be valid
14:39
<rbuckton>
I'd almost prefer just having module declarations over having both, if only to prevent potential confusion by adopters due to the inconsistency.
14:39
<nicolo-ribaudo>
If it isn't then we don't have the same kind of decl/expr parity that function and class have.
Functions already don't have parity - they have different scoping rules. Only classes can be transparently replaced with let className = class { ...
14:40
<danielrosenwasser>
So if you're running a in a context where globalThis.b exists, then a captured b takes precedence?
14:42
<rbuckton>
Functions already don't have parity - they have different scoping rules. Only classes can be transparently replaced with let className = class { ...
The difference in outer scoping is that a function declaration is hoisted, while a class has TDZ.
14:42
<Michael Ficarra>
we could just advance the error stacks proposal...
14:43
<Michael Ficarra>
this would be a very small change on top of that
14:49
<nicolo-ribaudo>

If you have this code:

module a { let b = getAModule(); await import(b); };
module b { await import(a); };

the dynamic import(b) imports the result of getAModule and not the outer declaration. That looks expected.

In this case:

module a { globalThis.b = getAModule(); await import(b); };
module b { await import(a); };

I see that it would be confusing, since globalThis.b = might be lexically somewhere else and not discoverable.

How would you feel if bindings of module declarations where not captured, but only statically importable?

module a {}
module b { import a; } // ok
module a {}
module b { console.log(a); } // ReferenceError: as every other type of bindings, you cannot capture bindings from the outer scope. You can only use them in import specifiers, which is not a generic "binding usage" position

Note: the current spec text throws the ReferenceError in this case, but just because it was easier to specify. We didn't have a reason to do one thing or the other, but this possible confusion seems to indicate what we should keep the error.

14:51
<Michael Ficarra>
this sounds like a topic for TG3...
14:52
<Michael Ficarra>
I'm not convinced by the hopelessness being expressed
14:53
<apaprocki>
Is this the only way that script can determine it's running in a shadow realm?
14:53
<apaprocki>
(currently known)
14:53
<Michael Ficarra>
I mean it's non-standard, there could be non-standard "amIRunningInAShadowReam" function
14:57
<nicolo-ribaudo>
(thanks btw danielrosenwasser and rbuckton for explaining your concerns with clear code examples!)
14:59
<danielrosenwasser>

If you have this code:

module a { let b = getAModule(); await import(b); };
module b { await import(a); };

the dynamic import(b) imports the result of getAModule and not the outer declaration. That looks expected.

In this case:

module a { globalThis.b = getAModule(); await import(b); };
module b { await import(a); };

I see that it would be confusing, since globalThis.b = might be lexically somewhere else and not discoverable.

How would you feel if bindings of module declarations where not captured, but only statically importable?

module a {}
module b { import a; } // ok
module a {}
module b { console.log(a); } // ReferenceError: as every other type of bindings, you cannot capture bindings from the outer scope. You can only use them in import specifiers, which is not a generic "binding usage" position

Note: the current spec text throws the ReferenceError in this case, but just because it was easier to specify. We didn't have a reason to do one thing or the other, but this possible confusion seems to indicate what we should keep the error.

I think if we had module declarations, I would prefer the "do not implicitly capture" behavior, but it feels like that defeats a lot of what would make them "special". I'm not sure if that's the right answer, but I found the current behavior confusing because my mental model was "modules capture nothing"
14:59
<apaprocki>
I mean it's non-standard, there could be non-standard "amIRunningInAShadowReam" function
Shipping a standard "amIRunningInAShadowRealm" would be the most extreme form of telling people script can change its behavior based upon how it is run, as opposed to relying on them reading spec text, implementation recommendation, or whatever it is called.
15:02
<Mathieu Hofman>
Here is the open issue about invariants: https://github.com/tc39/proposal-shadowrealm/issues/324
15:04
<caridy>
we could just advance the error stacks proposal...
perfect, I will drop the ball into that bucket by opening an issue there :)
15:05
<bakkot>
i had enough in me to stay awake until 7, barely, but I am very close to falling asleep before we finish this item
15:06
<Michael Ficarra>
commiserations to everyone who ended up in the overflow
16:49
<rbuckton>
Could a chair help me by finishing the transfer of https://github.com/tc39-transfer/proposal-async-explicit-resource-management to the tc39 org to help with the async resource management split?
19:42
<rbuckton>
Could a chair help me by finishing the transfer of https://github.com/tc39-transfer/proposal-async-explicit-resource-management to the tc39 org to help with the async resource management split?
I still need owner access so that I can make changes to Settings and set up CI/publishing for the rendered spec.
20:00
<ljharb>
rbuckton: done
20:01
<rbuckton>
Thanks!
21:59
<littledan>
I think if we had module declarations, I would prefer the "do not implicitly capture" behavior, but it feels like that defeats a lot of what would make them "special". I'm not sure if that's the right answer, but I found the current behavior confusing because my mental model was "modules capture nothing"

Honestly it sounds like we're starting to get at, module fragments should be in a separate namespace. Neither variables nor strings, but a secret third thing. As an obviously unusable strawman, let's say they begin with ## :). So the example would be:

module ##a { let b = getAModule(); await import(b); /* obviously targets the lexically scoped variable */ }
module ##b { import ##a; /* obviously targets the module declaration */ }
21:59
<littledan>
this separate namespace could actually be usable in expression context (to get the Module object), just not the opposite: it would be impossible to make a runtime variable with the same name
22:00
<littledan>
so then we'd have a clear explanation: module { } closes over just the ##xyz values, and nothing else
22:01
<littledan>

remember that we previously rejected strings specifiers for a couple reasons:

  • We wanted to leave 100% of the string specifier space to hosts. For example, we definitely didn't want to steal the fragment space of URLs, which already has other semantics.
  • At the same time, we wanted module declarations to have well-defined, reliable, host-independent semantics
22:03
<Alex Vincent>
rbuckton: I do want to confer with you sometime in December (but not in the next few days) about the resource management, cancellation, and proxy revocation proposals. Do you attend SES's strategy sessions occasionally?
22:24
<ljharb>
oof - a parallel namespace gives me all sorts of bad feelings. the committee reacted pretty strongly against that with hax's extensions proposal too, iirc
22:25
<littledan>
well, we made a separate namespace for private names :) Arguably the specifiers already are a separate namespace, as well. I think the viability depends a lot on if we can come up with an agreeable syntax.
22:26
<littledan>
also it depends whether a proposal "pays for itself" (introducing a significant enough new capability) and meets developer expectations (For extensions, I think developers hope it will magically work with x.y())
22:29
<ljharb>
i would think dev expectations of module fragments/blocks is that they either capture everything, like a function, or nothing at all (in which case the way to "pass in" something would be like in php's using declaration on closures)
22:32
<littledan>
Yeah, another way around this is another top-level production/mimetype (like web bundles, but it has to be processable just by the JS engine). That introduces other issues, though.
22:33
<littledan>
i would think dev expectations of module fragments/blocks is that they either capture everything, like a function, or nothing at all (in which case the way to "pass in" something would be like in php's using declaration on closures)
Honestly I don't understand how you arrived at such a strong analysis of developer expectations so quickly.
22:34
<ljharb>
"would think" is particularly strong?
22:34
<littledan>
never mind
22:35
<ljharb>
i would also think that any of us that have extensive experience interacting with devs in general would indeed have some sense, immediately, of what might be suitable, even tho more research is always helpful