15:05
<devsnek>
whoever is managing the schedule, the function tostring item is ready to be rescheduled for whenever works
17:03
<ystartsev>
quick reminder, we have #t39-beginners and i am doing descriptions of ongoing topics with a goal of explaining as much as possible
17:23
<akirose>
ystartsev: you should announce that in #tc39 too!
17:25
<leobalter>
I believe a different chat tool would be easier to handle multiple channels but IRC is not helpful as it does not show the many options.
17:26
<leobalter>
I don't have anything against more channels, but I believe it will eventually be very hard to find, even more for beginners. All because IRC
17:27
<ryzokuken>
leobalter: we can add a section to the reflector (and maybe to the introductory email) listing out all the channels perhaps?
17:28
<rkirsling>
<3 for changing away from upsert
17:28
<ryzokuken>
I say this because I 100% agree with the issue you point out, but the whole debate to change chat platforms is extremely bikeshed-dy.
17:28
<ryzokuken>
(and might take forever)
17:28
<leobalter>
ryzokuken: it's remains counterintuitive. It's a different tool and land. Even worse if you consider beginners might not have access to the reflector (yet?) or are not used to it
17:28
<rkirsling>
even if "emplace" is different from the C++ meaning, I'm just excited that it's a real word
17:29
<leobalter>
I don't see IRC as ideal for our chat, I'm not a big fan of any chat tool, but fragmentation of multiple channels without a good handling might have negative effects.
17:30
<leobalter>
it's unfortunate, because it's not at anyone's fault
17:30
<ryzokuken>
again, while I 100% agree, I think it would take quite a lot of back and forth discussion to actually choose an alternative and make the switch.
17:30
<leobalter>
oh, I see TC39 trying a new chat tool since I started participating
17:32
<leobalter>
I know I also don't want Slack, but I'd personally like discord. I believe it would be very hard to have consensus.
17:35
<ljharb>
ftr i abhor discord and love slack, so i agree it's a hard thing to solve
17:36
<devsnek>
i have a js discord server. its mostly full of noobs asking about discord.js but it could in theory host other discussions as well
17:51
<jridgewell>
Can we mute Brian please?
17:51
<michaelficarra>
bterlson: can you mute?
17:51
<jridgewell>
bterlson ^
17:56
<michaelficarra>
does anyone else hear "in-place" every time someone says "emplace"?
17:56
<jridgewell>
Someone kicked me from the meeting.
17:57
<akirose>
jridgewell: i kicked you on purpose. kind of. I kicked the "Unknown User"
17:57
<shu>
i agree with waldemar on emplace, we really should not name this emplace
17:58
<jridgewell>
I don't know how to set my name on the iPad app
17:58
<jridgewell>
i have it set in Teams
17:59
<drousso>
IIRC emplace does mean a slightly different thing in C++
17:59
<drousso>
... perhaps more than slightly actually
18:00
<ljharb>
jridgewell: i set it in the ipad app when clicking the teams link, before clicking "sign in as guest"
18:00
<ljharb>
shu: +1
18:01
<TabAtkins>
I will say that, for CSS purposes, .getDefault() will *not* help me, but update() will.
18:01
<TabAtkins>
(Properties always exist on the property maps.)
18:01
<jridgewell>
Ahh, signing up for Teams was a mistake then.
18:01
<akirose>
yay ty jridgewell !!
18:01
<jridgewell>
Will try again as a guest
18:07
<TabAtkins>
Oh, hm, calling `this.update()` from the insert() is an interesting case for the handler pattern.
18:09
<TabAtkins>
What's bradley's irc nick?
18:09
<Bakkot>
bradleymeck
18:10
<TabAtkins>
Ah, skipped that because their last name is Farias.
18:10
<TabAtkins>
bradleymeck: Yo, on review I see you *do* have a section in the proposal explicitly for "update only", I just missed it on my first quick read, sorry about that.
18:10
<bradleymeck>
k
18:14
<bterlson>
ljharb: Iterable is definitely a noun? An iterable, as distinct from an iterator?
18:14
<devsnek>
iterable is any object with Symbol.iterator
18:14
<devsnek>
it doesn't prescribe a behaviour beyond that
18:15
<bterlson>
"An iterable isn't a noun" πŸ€”
18:15
<devsnek>
this is the same issue we had with iterator helpers
18:15
<devsnek>
arguing about whether they should be iterable helpers
18:17
<ljharb>
bterlson: as a noun, you're right. but it really means "it's one of the things that are iterable"
18:17
<bterlson>
An iterator is the same story isn't it?
18:18
<ljharb>
i suppose that's true, but the iterator helpers proposal makes a canonical Iterator
18:18
<ljharb>
and that's what i'd expect Number.range to return
18:19
<rickbutton>
^ I wouldn't expect that. I would expect what MM expects, that the return of `Number.range()` could be iterated multiple times
18:19
<ljharb>
rickbutton: the return of .keys/values/entries on arrays/maps/sets, and .matchAll, can't be
18:19
<ljharb>
and neither can the iterator produced by a userland generator
18:20
<ljharb>
iow, "can iterate multiple times" is an expectation that will already bite you in a ton of places
18:20
<rickbutton>
that's true, but I guess I'm using the mental model of a concrete `Range` object that is safe to iterate many times, the same way you can iterate an `Array` many times
18:21
<jridgewell>
I like devsnek's reasoning.
18:21
<ljharb>
+1
18:21
<jridgewell>
If it's a constructor, then it should be an iterable
18:21
<ljharb>
agreed
18:21
<jridgewell>
If it's a function, it should be an iterator
18:21
<rickbutton>
like, in a language without iterators, I would assume `Number.range(a,b)` would return an `Array` with values from a->b
18:22
<ljharb>
rickbutton: as would i. but this language has them
18:22
<TabAtkins>
I'm also mildly in favor of `range(n)` == `range(0,n)` - I won't die if it's not there, but it's useful and clear.
18:22
<ljharb>
it's the same reason matchAll didn't return an array
18:22
<TabAtkins>
jridgewell: +1, yeah, if it's an iterator we *have* to go all the way to a class, or else the design feels incoherent
18:22
<rickbutton>
I agree with function vs constructor => iterator vs iterable
18:22
<TabAtkins>
UGH *iterable
18:22
<rickbutton>
(as a design pattern)
18:22
<TabAtkins>
yup
18:24
<jridgewell>
So I think we need to look at the call pattern.
18:24
<devsnek>
i also think sticking a function on the front is good from the perspective of being clear about when you're reusing something
18:24
<jridgewell>
It's used as `range(…)`, not `new Range(…)`
18:24
<ljharb>
^
18:25
<rkirsling>
agree with "big missing piece"
18:28
<TabAtkins>
Strong +1 to Waldemar's upcoming question about start==end with inclusive; it should return the value once.
18:29
<TabAtkins>
I use range() a lot in Python, and I *have absolutey no idea* whether it's reusable or not. I have literally never once stored a range in a variable.
18:30
<rkirsling>
^ ditto πŸ˜…
18:30
<Bakkot>
same
18:31
<shu>
i think that observation is the key one
18:31
<shu>
tab's, that is. the reusability sticking point is a red herring in practice
18:31
<devsnek>
i tried to note in the issue that it almost never happens
18:31
<devsnek>
because people treat them as logic
18:31
<devsnek>
not data
18:32
<TabAtkins>
Wait that's a lie, I'm looking at a line where I do store a range in a variable (so I can manually increment it in the following loop), but it's not reused.
18:33
<devsnek>
wow
18:33
<TabAtkins>
i'm sorry for disappointing you, devsnek
18:33
<ljharb>
TabAtkins: would it be prohibitive to stick it in a function if you needed that in JS?
18:34
<TabAtkins>
absolutely not, i think "wrap it in a function" is completely reasonable here
18:34
<jridgewell>
wsdferdksl: Can you open an issue for your queue item
18:34
<jridgewell>
I think it should be addressed
18:34
<devsnek>
hax is the only person i know that isn't on board with "wrap it in a function"
18:35
<bterlson>
sorry to be a time stickler friends, but we're over time. Reminder to pick conservative timeboxes if you definitely want your proposal to advance πŸ˜€
18:35
<devsnek>
actually maybe mark as well
18:35
<devsnek>
couldn't tell
18:37
<TabAtkins>
Hmm. Yeah, these operators just avoid `Promise` and `()`. That's minor, but also, I can easily see it actually being mildly significant in a codebase that's heavily async.
18:38
<devsnek>
ljharb: stack traces in v8 already have nice stuff for Promise.all
18:38
<Bakkot>
if they always use the built-in promise operations there's probably some performance implications as well
18:39
<devsnek>
it will tell you which index the error came from
18:39
<michaelficarra>
yeah I think it's pretty cool but syntax is soooo expensive, I don't know if this is one of those things we need to push on language learners
18:39
<michaelficarra>
Bakkot: it'd be cool to see those numbers
18:40
<ljharb>
devsnek: that is nice
18:40
<shu>
michaelficarra: Bakkot: my intuition here is that the performance cost promise combinators is not the synchronous logic of the combinator
18:40
<ljharb>
people *really* like using the shiny await syntax
18:40
<shu>
speeding that up probably won't help
18:40
<TabAtkins>
michaelficarra: On the other hand, idents/property access I think is the lightest possible syntax addition? Readable and even searchable, unlike grawlix operators.
18:40
<ljharb>
and by having to use Promise.all to gain concurrency, a lot of code is unnecessarily sequential
18:40
<ljharb>
providing await syntax for it would go a long way imo to improve that
18:40
<shu>
ljharb: i don't understand that point, how does this add new concurrency?
18:41
<ystartsev>
I am also not sure on that point
18:41
<devsnek>
what would be more interesting is for-await-concurrent
18:41
<shu>
sure, but that's a different proposal
18:41
<ljharb>
shu: `await.all x` over `await Promise.all(x)` is identically concurrent
18:42
<shu>
ljharb: you said "a lot of code is unnecessarily sequential", implying new syntax will make them... concurrent?
18:42
<ljharb>
shu: i'm saying that people are more likely to *use* Promise.all semantics if there's syntax for it, because they *really* like using `await` and are under the misimpression that they don't have to use Promise things when using it
18:42
<ljharb>
shu: i'm saying there's a lot of places people are doing `await x; await y;` where they could do `await.all [x, y]` instead (or `await Promise.all([x, y])` instead), and they're more likely to do that change if it's got syntax
18:43
<ljharb>
the current scenario is that `await` is an attractive nuisance *because* it makes it too easy to avoid properly using `Promise.all`
18:43
<TabAtkins>
ljharb: Agree, I think the syntax affordance, while *relatively* minor, can easily have outsized effects on actual usage.
18:43
<shu>
hm, okay, that's an interesting point
18:44
<shu>
that dan's making now i guess
18:44
<devsnek>
`await.all ArrayLiteral` doesn't feel right to me yet
18:44
<devsnek>
but i like the general idea
18:45
<TabAtkins>
I mean, [] is the only way to invoke an n-ary operator, I guess.
18:46
<TabAtkins>
fwiw, I suspect the "use original value but accidentally call into customizable stuff" is accidental - it probably *wants* to just use purely original stuff, like `await` does. (And I think it should do that.)
18:46
<Bakkot>
we could make `await.all (a, b)` work, but I don't seem much reason to
18:46
<Bakkot>
the array literal is more orthogonal
18:46
<devsnek>
i'm imagining some sort of block thing
18:47
<devsnek>
but i don't have it worked out yet
18:47
<TabAtkins>
Yeah, given that Promise.all() takes an array, having the syntax take n-ary args instead would be a very bad thing
18:48
<rkirsling>
just say no to varargs
18:49
<shu>
i still don't feel convinced that await.all significantly improves discoverability
18:49
<TabAtkins>
Sigh, if only we had named args...
18:49
<shu>
that's what the argument comes down to, right?
18:49
<devsnek>
TabAtkins: we do!
18:49
<shu>
that folks using await are simply unaware that it's promises under the hood
18:49
<drousso>
^ +1
18:50
<TabAtkins>
shu: I don't understand what you're saying.
18:50
<devsnek>
the assumption is that they don't know about promises
18:50
<ljharb>
shu: yes
18:50
<devsnek>
but know about await
18:50
<ljharb>
shu: that is accurate
18:50
<rkirsling>
yeah I would rather educate
18:50
<shu>
TabAtkins: the argument put forth by dan and jordan is that today, because devs know about async/await and _only_ know about async/await, they never learn it's all Promises under the hood
18:50
<ljharb>
i tell about 30 people a week on irc, every week for 5 years now, that `async`/`await` is not a replacement for promises, and that they still need to understand promises to use it properly.
18:50
<TabAtkins>
"dont' know about promises" is not equal to "reaches for linear awaits rather than Promise.all(), by default"; the latter is the arg here.
18:50
<rkirsling>
I don't think this is necessarily more obvious, because it's syntax that no one would expect to exist
18:51
<ljharb>
it's not that they'll discover it
18:51
<ljharb>
it's that they can be told to use it, via review or a linter
18:51
<shu>
TabAtkins: why do they reach for the linear awaits rather than Promise.all if they are aware of promises?
18:51
<rkirsling>
fair
18:51
<TabAtkins>
It really is just easier to type `await p1; await p2;` than `await Promise.all([p1, p2]);`
18:51
<ljharb>
shu: because most people don't seem to understand that promises are like a dependency graph, and that you should only await something when there's no additional work to kick off
18:51
<rkirsling>
ljharb: on that note I had somebody talk about hitting no-return-await this morning and it was somebody I definitely thought should have known
18:51
<ljharb>
yep
18:52
<TabAtkins>
Without counting chars I can't tell if *three* promises in a row is shorter or longer than P.all(), but it's still *easier* to type - in particular, no `([...])` to type, which is slightly tricky.
18:52
<ljharb>
i mean, eslint even has a `require-await` rule, which is totally nonsensical, because the eslint maintainers didn't seem to understand async/await properly at the time.
18:52
<devsnek>
are people just as likely to write their promise chains needlessly flat
18:53
<drousso>
heck, i see code all the time like `async function foo(promise) { let result = await promise; return result; }`
18:53
<ljharb>
with promises, i find people are more likely to write things as separate variables than in a single long chain (which is what an async function full of `await`s is)
18:53
<TabAtkins>
yup, same intuition here.
18:53
<Bakkot>
https://es.discourse.group/t/array-prototype-uniqby/138
18:54
<shu>
ljharb: TabAtkins: thanks
18:56
<TabAtkins>
This slope really isn't that slippery.
18:57
<akirose>
I'm sorry I don't endorse hating on programming languages but i really do hate that about teaching/learning Ruby
18:58
<devsnek>
what was the note about ruby
18:58
<keith_miller>
to be fair you could probably make filterMap much faster than filter + map
18:59
<keith_miller>
since you don't have to iterate the array twice
18:59
<TabAtkins>
(I just had a WG discussion earlier today about adding more numeric constants to CSS (we already have e and pi); dealing with slippery-slope is a basic requirement of language design, not something we can or should ever be absolute about.)
18:59
<jridgewell>
`flatMap`?
18:59
<TabAtkins>
Yeah, filterMap() is actually specifically an example I'd use as something to *add* ^_^
18:59
<akirose>
devsnek: there's a method for everything
19:00
<drousso>
does that mean we now need to do combinatorics of every prototype method?
19:00
<Bakkot>
jridgewell flatMap is a single operation for people coming from a fp background; I don't think that's true of filterMap etc
19:00
<drousso>
`filterMap` and `mapFilter`?
19:00
<keith_miller>
then again there are all kinds of weird performance pathologies in the std library design I tell people fix on their own
19:00
<jridgewell>
Who would you differentiate between "remove this" and "false"?
19:00
<jridgewell>
how**
19:00
<TabAtkins>
jridgewell: sentinel value provided to the callback
19:00
<keith_miller>
My favorite is that i tell everyone to relpace all the typed array methods with a per type one
19:01
<keith_miller>
Because you don't get hit with the polymorphism perf hit
19:01
<TabAtkins>
drousso: "if we do X, doesn't that mean we'd have to do combinations(X)" is precisely the argument I was just saying isn't a valid argument here; applying judgement here on where the line is is already a basic part of language design.
19:01
<drousso>
oh yeah no i agree
19:01
<shu>
keith_miller: pre-fused methods seems fine to me tbh if they're common enough
19:02
<drousso>
i should've been clearer i was being more of a "devils advocate to prove the problem" πŸ˜…
19:02
<rkirsling>
this reminds me of how Dart's standard library has .splitMapJoin()
19:03
<rkirsling>
ljharb: stepping back a conversation, what about having the linter complain about `await p1; await p2;` in current code?
19:03
<TabAtkins>
drousso: Right, it's just never a useful objection to bring up at all, imo. "If we add this, what else hits the same line? Is it a lot? Are we okay with that?" is reasonable, but usually instead it's presented as an ipso facto rejection.
19:04
<drousso>
i do think there is a very valid argument for "if this passes muster, what are the criteria for other things passing that muster"
19:04
<TabAtkins>
btw, i'm signing off the meeting for the rest of the afternoon, got prep work for CSS f2f next week. (I'll still be in chat, just saying I won't be in convos.)
19:04
<drousso>
I do agree that "if we do X then we need to combinations(X)" is not helpful
19:04
<TabAtkins>
drousso: Right. Say that, instead, and I'll be happy. ^_^
19:05
<drousso>
fair
19:05
<TabAtkins>
(Basically I've become very wary and alert, after twelve years of standards work, for fully-general counterarguments. If you apply the argument to virtually any topic with no change, it's not an argument, it's stop energy.)
19:06
<TabAtkins>
*if you can apply
19:08
<shu>
ljharb: i'd still love to see some examples of people unwittingly serial awaiting where there is no inherent serialization required
19:09
<TabAtkins>
oh that should be very easy to find
19:09
<shu>
my experience is that serialization is rarely wrong, and that may be another reason why it's reached for by default
19:09
<shu>
whereas you usually have to do a lot more thinking to make things parallel
19:10
<TabAtkins>
almost by definition, serializing is almost never *wrong* compared with Promise.all()
19:10
<shu>
well, precisely
19:12
<shu>
this DX argument talks about an affordance so Promise combinators are easier to reach for, and if inadvertent incorrectness is a factor, that should affect the argument
19:12
<shu>
bbl lunch
19:13
<ljharb>
rkirsling: sometimes that's actually desired tho
19:14
<ljharb>
shu: i see lots of things like `const a = await getA(); const b = await getB(); const c = await getC(B);` which should be `const [a, b] = await Promise.all([getA(), getB()]); const c = await getC(B);` (and even that's unnecessarily serial if what follows the `c` declaration doesn't actually require a, b, or c)
19:14
<ljharb>
iow, i'd say serialization is wrong if it's not necessary
19:15
<Bakkot>
fwiw I almost never encounter this; glancing around my codebases it's all very linear data dependencies, at least within an individual function
19:16
<ljharb>
i'm sure it varies by codebase domain, and individual programmer mindset
19:17
<ljharb>
but i'm often writing code where i have to make multiple async requests for data, but they can be intermixed in ways that increase concurrency.
19:17
<ljharb>
like, 2 requests require no input, 2 requests require input from 1 of the first one, and a final request requires 3 of the 4 results, or something
19:18
<devsnek>
how about a native api for limiting concurrency
19:19
<devsnek>
i guess that's an option for Promise.all
19:19
<TabAtkins>
Isn't that just what the combinators/chaining already are?
19:19
<Bakkot>
not really for Promise.all
19:19
<Bakkot>
Prome.all takes a set of promises for results which are already being computed
19:19
<devsnek>
oh right no since you pass the already existing promises to promise.all
19:20
<devsnek>
so yeah concurrent scheduling api
19:20
<devsnek>
class Promise.Queue
19:21
<TabAtkins>
Right. What I mean is, Promise.all() is already "everything's concurrent" and Promise#then() is already "these two are serial". So all the tools are already there. A more ergonomic API for setting up a dag out of promises might be reasonable, tho.
19:22
<Bakkot>
out of async functions, not promises
19:22
<devsnek>
TabAtkins: its more like saying, only have 4 http requests going at once
19:22
<devsnek>
you'd set up a queue that schedules when the async functions are called
19:23
<TabAtkins>
Ah, ok, yeah that's a useful thing (tho I think beyond the scope of this discussion?)
19:23
<devsnek>
yeah it is
19:23
<devsnek>
i just momentarily messed up the abstraction in my head
19:23
<devsnek>
re saying it was an option for promise.all
19:23
<TabAtkins>
Bakkot: Oh, getting async function composition, hm.
19:25
<TabAtkins>
Like, the problem here is that the "best" way to handle async stuff is to *always* store async values in promise variables, and then *only* await them at the moment they're needed. If you do that, you can even do serial awaiting without a problem - you've already kicked off the operations, so serially awaiting the results is just fine (difference is just a few microtasks).
19:26
<TabAtkins>
But people dont' do that, they await the operation immediately because it lets them continue to think about their code in sync terms, which is totally reasonable - asynchrony is *hard*.
19:27
<TabAtkins>
So anything that makes it easier for people to reach for better asynchrony at the point of making async function calls is good, I think. `await.all` feels like it would help there, I think. (but i don't have strong feelings about it yet)
19:30
<TabAtkins>
(I was going to suggest that having an op that let you more easily treat a promise as its value at the point of use might help, but that's literally just `await` already. We just, uh, kinda screwed up the ergonomics of `await` from the get-go.)
19:44
<devsnek>
nah await is good
19:46
<ljharb>
i agree with tab, `await` is way too easy to misuse in my experience.
19:47
<TabAtkins>
await has *awful* ergonomics, because it's a low-precedence prefix operator.
19:48
<TabAtkins>
Can't mix it into a method chain without terribly awkward contortions, for instance.
19:48
<littledan>
about await* -- I really don't like the * use in generators; it feels like a mistake to me. i wish we'd use spread instead.
19:48
<TabAtkins>
foo.bar().asyncaz()
19:48
<littledan>
await ...arr
19:48
<TabAtkins>
`foo.bar().asyncBaz().qux()` is instead `(await foo.bar().asyncBaz()).qux()`, just awful
19:49
<TabAtkins>
I think Rust *slightly* screwed up their solution, but it's overall the right direction.
19:49
<Bakkot>
hence pipeline I guess
19:49
<TabAtkins>
Pipeline does make it a bit easier, yeah
19:50
<TabAtkins>
Eh, scratch that, pipeline basically solves that problem, yeah.
19:50
<rickbutton>
shu: your mic is enabled, if you didn't know
19:50
<ljharb>
TabAtkins: in fact, my advice to all newcomers is "start with no promise chains and no `await`s whatsoever. make a new variable for every `.then`/`.catch`. then, once everything's tested and working, refactor to use Promise.all _wherever possible_. then, make chains. and only *then*, use `await` in front of each chain
19:50
<TabAtkins>
ljharb: That's great advice, I wish everyone followed it. ^_^
19:50
<shu>
rickbutton: i have a hardware mute
19:51
<rickbutton>
+1
19:51
<shu>
thank you
19:51
<Bakkot>
I tell newcomers the exact opposite of that
19:52
<leobalter>
shu rickbutton on the hardware mute, Shush is a very nice app if you're on MacOS
19:52
<Bakkot>
write it with sequential `await`s first so the logic is clear, refactor to use `Promise.all` afterwards only when you have a reason to
19:52
<Bakkot>
premature optimization is the root of all evil
19:52
<rickbutton>
nice, TIL leobalter
19:52
<Bakkot>
more important to make the logic clear and correct
19:53
<TabAtkins>
"premature optimization *and* casual deoptimization are both pretty bad evils" is my feeling, honestly
19:55
<rkirsling>
"yeah I deopt, but I like to keep it casual"
19:59
<ljharb>
ime the logic - which includes the implied dependency graph - is clearer to newcomers with promises
19:59
<ljharb>
but obv both of our positions are anecdotal and subjective
20:04
<michaelficarra>
this still doesn't support arbitrary compound keys though, since they can only contain value types
20:05
<devsnek>
it could if it didn't disallow objects
20:05
<michaelficarra>
although to be fair I think they have a solution to that by representing an object's identity
20:06
<devsnek>
i'm hoping we can at least get boxes
20:06
<devsnek>
i wouldn't consider symbol weakmap stuff to be the solution here
20:08
<bradleymeck>
i came in late did this state that the value is normalized to a +0 value or if they are just equal for ==/===
20:08
<shu>
bradleymeck: pretty sure the latter
20:08
<devsnek>
bradleymeck: it consider -0 === +0
20:08
<devsnek>
does not normalize
20:08
<devsnek>
it also considers NaN === NaN
20:11
<robpalme>
@wsdferdksl the earlier slide showed that order of entries in a record is *not* significant for equality
20:12
<Bakkot>
for which reason Symbols are disallowed as record keys
20:24
<shu>
my computer just froze
20:24
<devsnek>
why are symbols as record keys not possible?
20:24
<shu>
Bakkot: i have comments ofc but i am restarting
20:24
<ljharb>
devsnek: that's my queue question - i think it's because there's no way to sort them.
20:24
<devsnek>
ljharb: what does "sort" mean
20:25
<ljharb>
devsnek: as in, sorting the keys
20:25
<devsnek>
why do keys need to be sorted
20:25
<ljharb>
so that `#{ a: 1, b: 2 }` and `#{ b: 2, a: 1 }` are equivalent
20:25
<Bakkot>
https://github.com/tc39/proposal-record-tuple/issues/15#issuecomment-662135746
20:25
<Bakkot>
https://github.com/tc39/proposal-record-tuple/issues/15#issuecomment-662415531
20:26
<devsnek>
the order of getOwnPropertySymbols seems very unmotivating
20:26
<ljharb>
"what the order is", surely. but "that it's consistent"? i think it's pretty important
20:26
<devsnek>
generally its just weird to me
20:26
<Bakkot>
as in, you are OK with Object.is(a, b) being true but getOwnPropertySymbols(a) not being the same as getOwnPropertySymbols(b)?
20:26
<devsnek>
that the order has to be part of this
20:27
<devsnek>
like you shouldn't need to sort the non-symbol keys either
20:27
<devsnek>
Bakkot: yeah totally 100%
20:27
<devsnek>
same for Object.keys
20:27
<Bakkot>
mm
20:27
<Bakkot>
well
20:27
<Bakkot>
that is an opinion one could hold
20:27
<devsnek>
lol
20:27
<ljharb>
devsnek: two records with different orders and the same keys shouldn't be distinguishable tho.
20:28
<devsnek>
are people determining identity by the order of keys on things?
20:28
<Bakkot>
the other way around
20:28
<Bakkot>
people assume that if a and b are the same, then then are indistinguishable
20:28
<Bakkot>
this being what it means to be "the same"
20:28
<Bakkot>
A is A
20:28
<devsnek>
it seems to me that they're the same even if the keys are in different orders
20:29
<ljharb>
i don't hold that intuition
20:29
<Bakkot>
if they are distinguishable they are not that same
20:29
<Bakkot>
*not the same
20:29
<ljharb>
things are the same only when every single aspect of them are identical
20:29
<ljharb>
otherwise they can only be similar, not the same
20:29
<michaelficarra>
an engine can have a stable sorting of symbols
20:30
<michaelficarra>
as littledan is explaining right now
20:30
<ljharb>
how would we specify that tho
20:30
<devsnek>
what is mark saying rn
20:30
<devsnek>
side channel through symbol keys?
20:30
<Bakkot>
ljharb gloabl incrementing counter works fine
20:30
<ljharb>
without communicating "symbol creation time"
20:31
<devsnek>
we could put a counter on them
20:31
<ljharb>
like if i do `const a = Symbol(); const b = Symbol(); return [b, a]` you shouldn't be able to determine that i made `a` first
20:31
<devsnek>
agent.symbolcounter
20:31
<Bakkot>
devsnek I believe the example is https://github.com/tc39/proposal-record-tuple/issues/15#issuecomment-662545733
20:31
<shu>
my computer froze at a most inopportune
20:31
<shu>
time
20:31
<shu>
bakkot: what was said about implementer concerns?
20:31
<devsnek>
ah i see
20:31
<ystartsev>
we had a similar concern to this as well
20:31
<devsnek>
Bakkot: in terms of implementation i was thinking of using the value's hash code to sort
20:31
<Bakkot>
shu: I said I want implementation buy in before stage 3, ystartsev said they've been talking about it and are tentatively in favor, moddable said they're concerned
20:32
<ystartsev>
the object/record issue
20:32
<rkirsling>
"fatigue" is a very good word
20:32
<devsnek>
that wouldn't reveal their creation order
20:32
<shu>
Bakkot: okay thanks
20:32
<ljharb>
devsnek: how would two symbols with the same description have different hash codes, but not reveal creation order
20:32
<ljharb>
devsnek: and also not be random
20:32
<ystartsev>
Bakkot: yep, we have been talking to them a lot and giving feedback. we aren't 100% "yes this should happen", but we do see the motivation and think its worth investing more time / giving it stage 2 (just to reiterate)
20:32
<Bakkot>
devsnek it would probably reveal memory addresses, which is worse
20:32
<Bakkot>
devsnek that's like an actual security issue
20:33
<devsnek>
hashes aren't memory addresses
20:33
<Bakkot>
for objects with identity they frequently are
20:33
<ljharb>
if they're not deterministically based on observable traits, how could they not be
20:33
<devsnek>
iirc v8 chooses them from a pseudorandom number generator
20:33
<michaelficarra>
Bakkot: literally hashing the counter?
20:33
<michaelficarra>
or using a PRNG, another fair example
20:33
<Bakkot>
michaelficarra hashing the counter would work, but if you have a counter, just use the counter, and then it's stable across engines too
20:34
<Bakkot>
though I guess leaks the thing
20:34
<Bakkot>
personally I kind of like random-but-stable ordering; I proposed it in the above issue I think
20:34
<Bakkot>
maybe I proposed random-per-read
20:34
<devsnek>
found it https://source.chromium.org/chromium/chromium/src/+/master:v8/src/execution/isolate.cc;drc=0ee4438ca83448bca93cb11bc3012856d29303dd;l=3893
20:35
<Bakkot>
devsnek yeah they do that because of exactly the security issue I mention, I am pretty sure
20:35
<michaelficarra>
I reject Mark's claim that it necessarily leads to a communications channel
20:35
<devsnek>
if there was no ordering it would be a communication channel
20:35
<haxjs>
Is the "Equality semantics for `-0` and `NaN`"(https://github.com/tc39/proposal-record-tuple/issues/65) postponed to be decided at stage3 so it's not a concern to have consensus at this stage? It's been quite a hot debate (154 comments).
20:35
<Bakkot>
devsnek but compare: https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#hashCode--
20:35
<ljharb>
haxjs: afaict it's decided
20:35
<Bakkot>
"This is typically implemented by converting the internal address of the object into an integer"
20:35
<devsnek>
Bakkot: lets not do what java does
20:35
<ljharb>
haxjs: SameValueZero, basically
20:35
<Bakkot>
devsnek :P fair
20:36
<Bakkot>
devsnek but that means we have to write something down which isn't "do whatever you want", is my point
20:36
<Bakkot>
because the obvious whatever-you-want thing is memory address
20:36
<haxjs>
ljharb it is labeled as "undecided point"
20:36
<michaelficarra>
I think we should allow implementations to use the memory address if they want
20:37
<ljharb>
haxjs: ah. if it gets stage 2 with those semantics i would take that as decided
20:37
<michaelficarra>
Moddable may choose to use the memory address, for example
20:37
<ljharb>
haxjs: but i suppose it could change within stage 2 if needed
20:37
<devsnek>
Bakkot: "a unique number which is not correlated with the object's location in memory or when it was created"
20:37
<devsnek>
:P
20:37
<Bakkot>
works for me tbh
20:37
<ljharb>
what is "memory" according to the spec
20:38
<Bakkot>
spec does not have the concept
20:38
<Bakkot>
this actual wording wouldn't work
20:38
<devsnek>
does atomics not talk about memory
20:38
<Bakkot>
but we could find something like it
20:38
<Bakkot>
devsnek nope
20:38
<Bakkot>
well
20:38
<devsnek>
e
20:38
<Bakkot>
it has a memory model
20:38
<Bakkot>
but not in the sense which is relevant here
20:38
<devsnek>
we could definitely specify this
20:38
<devsnek>
is the point
20:38
<michaelficarra>
that V8 identity hash is very slightly biased to 1
20:38
howdoi
whoever is anonymous wombat, you are doing an awesome job is taking note, my hands are burning!
20:39
<devsnek>
michaelficarra: report to h1
20:39
<devsnek>
:^)
20:39
<shu>
i am not sure that we can _definitely_ spec "memory" writ large
20:40
<devsnek>
we can say that it shouldn't be correlated to when the object was created
20:40
<devsnek>
and leave security to implementors
20:40
<michaelficarra>
devsnek: +1
20:40
<michaelficarra>
not all implementors have the same security model
20:40
<devsnek>
its my right to make a js engine that can be pwned
20:41
<ljharb>
normative note: must not allow pwnage
20:41
<shu>
it's your right to catch covid too i guess
20:41
<Bakkot>
that depends on your system of morality
20:41
<devsnek>
this feels like tdz now
20:42
<Bakkot>
michaelficarra / devsnek if you have a concrete way of specing symbols in records which wouldn't violate either the "Object.is implies indistinguishable" constraint or the "does not side channel symbol creation time" constraint, I think it would be worth opening on the issue tracker
20:43
<Bakkot>
though it would probably be a followon proposal at this point
20:56
<wsdferdksl>
brad4d: The reason symbols were disallowed was because there was no way to ever make a symbol "weak" if one can reconstruct it at will.
20:56
<bradleymeck>
can we stay on the current proposal, is that a point of order
20:57
<wsdferdksl>
This led to the debate over whether we should disallow some symbols and no others, and after a while we decided to not allow any.
20:58
<wsdferdksl>
s/no/not/
20:58
<devsnek>
there has to be some sort of thing
20:58
<devsnek>
that fulfills being a nice api
20:58
<devsnek>
and the ses requirements
21:01
<devsnek>
oh man i would love if r&t could just hold objects
21:04
<devsnek>
you don't lose the performance of deeply immutable by it referencing an object do you?
21:04
<Bakkot>
the benefit of deep immutable to performance is that you can safely pass it around without having copying
21:04
<Bakkot>
if it holds an object that is no longer the case, and you have to defensively copy all records and tuples
21:04
<devsnek>
how is that the case
21:05
<Bakkot>
which of those two things?
21:05
<devsnek>
the loss of no-copy
21:05
<Bakkot>
uh
21:05
<Bakkot>
the point of defensive copying is that you can hand it to someone you don't trust, and not worry about your data getting mangled
21:05
<Bakkot>
if you hand them a pointer to a mutable thing you still want to use, you have to worry about that
21:06
<ljharb>
but if you put an unfrozen object in your record/tuple, aren't you already explicitly ok with that?
21:06
<Bakkot>
even if the pointer is embedded in an immutable thing
21:06
<devsnek>
oh so you weren't answering the performance question
21:06
<Bakkot>
I was answering the performacne question
21:06
<Bakkot>
the performance benefit is, you don't have to defensive-copy to avoid this worry
21:06
<devsnek>
you still don't have to defensively copy the record
21:06
<devsnek>
its immutable
21:07
<Bakkot>
if it holds an object, then you do have to
21:07
<Bakkot>
(deeply)
21:07
<Bakkot>
because handing it out gives you access to a mutable thing
21:07
<devsnek>
don't put the secure part in an object
21:07
<devsnek>
just because you can have an object in a record doesn't mean you have to
21:08
<Bakkot>
yeah it just means you have to be defensive all the time, instead of being able to trust that records and tuples are safe to hand around
21:08
<devsnek>
i don't understand what you're defending against
21:08
<devsnek>
people forgetting that something is an object?
21:09
<devsnek>
you still have to defensively remember to use records
21:09
<Bakkot>
if my API takes records, I can use them and hand them to other people without worrying about breaking my caller's guarantees
21:09
<Bakkot>
if it takes records but records can hold objects, this is not the case
21:09
<devsnek>
that breaks the guarantee that you can pass an object
21:10
<devsnek>
my point being that there are a lot of tradeoffs here
21:10
<Bakkot>
how does it break that guarantee?
21:10
<Bakkot>
or like
21:10
<Bakkot>
what guarantee are you talking about, I guess
21:10
<devsnek>
the ability to pass an object
21:11
<devsnek>
also why is it your api's problem if the caller passes a mutable structure
21:11
<Bakkot>
I can take an object just fine I just have to deep-copy it first, which I do not have to do with records
21:12
<devsnek>
why do you have to deep copy it
21:12
<devsnek>
its the caller's object
21:12
<devsnek>
maybe they already deep copied it before giving it to you
21:12
<Bakkot>
it is my API's problem if it mutates things its caller expects not to be mutated
21:12
<devsnek>
don't do that
21:12
<ljharb>
mutating an object you didn't create doesn't seem like a defensible practice
21:12
<Bakkot>
right, the point is I am trying to avoid doing this
21:12
<ljharb>
and if it's a record you can't mutate it anyways
21:13
<Bakkot>
right, the point is that I don't have to worry about it if it's a record
21:13
<Bakkot>
that is the whole point
21:13
<devsnek>
i don't understand how <random thing from person> ends up being mutated
21:13
<ljharb>
if you're not mutating it, you don't have to worry regardless
21:13
<ljharb>
and if you are, you can't take a record anyways
21:13
<ljharb>
i'm confused
21:13
<devsnek>
"oops i accidentally created a property on the value" i don't get how this would happen
21:13
<Bakkot>
this happens if I am handing it off to other code
21:13
<ljharb>
sure, *that* code might mutate it
21:14
<devsnek>
the security of the object was not yours to begin with
21:14
<ljharb>
but similarly, it can't take a record if it's doing that
21:14
<devsnek>
it was whoever gave it to you
21:14
<Bakkot>
devsnek that is a position one can take, but I am very glad that position is not widely held among library authors
21:14
<ljharb>
so to give it something and have it not mutate it, you already have to know if "it mutates"
21:15
<ljharb>
i mean, i don't *want* anything someone gives me to be mutated, even transitively
21:15
<Bakkot>
ljharb if the other code takes a record, I don't have to know that!
21:15
<devsnek>
i would be very sad to know the only reason library authors aren't mutating things is because they *can't*
21:15
<sffc>
ystartsev: Thanks for keeping the symbols as weakmap keys proposal honest on Stage 2 requirements. We need to do better about not deviating from the stage process.
21:15
<ljharb>
Bakkot: so you're saying, this relieves a code auditing burden of your deps?
21:15
<Bakkot>
ljharb if the other code takes a record, I can trust that it is not going to accidentally mutate stuff, without checking its implementation
21:15
<Bakkot>
yes
21:15
<haxjs>
could we give object in record/tuple a special syntax to avoid such problem?
21:15
<Bakkot>
no one audits their deps
21:15
<devsnek>
i still don't get the whole "accidentally mutate" thing
21:16
<ljharb>
Bakkot: i do :-p but i'm not confused now, thanks
21:16
<Bakkot>
ljharb I have seen you contribute to projects for which I am 100% confident you have not read all the code in the transitive dependency graph of the project
21:17
<devsnek>
probably doesn't have security concerns about those projects?
21:17
<Bakkot>
it's not a security concern
21:17
<Bakkot>
it's a correctness concern
21:17
<devsnek>
you said it was a security concern
21:17
<Bakkot>
I did not
21:17
<ljharb>
Bakkot: contribute to, sure, but maintain? i should hope not
21:17
<ljharb>
Bakkot: please lmk if that's not true
21:17
<devsnek>
if i'm going to accidentally mutate something
21:17
<devsnek>
why don't i accidentally forget to enforce it being a record
21:18
<devsnek>
you don't solve forgetfulness by adding self-checked requirements
21:19
<Bakkot>
devsnek: if I am calling a library, then either I need to a.) trust it not to mutate the object, b.) check its implementation, or c.) be guaranteed that it cannot mutate the object because it takes records rather than objects
21:19
<Bakkot>
a.) and b.) both suck
21:19
<Bakkot>
c.) only works if "it takes records" implies "it cannot mutate its arguments"
21:19
<devsnek>
i'm not sure i've ever had this problem
21:19
<devsnek>
like i don't feel the need to ensure libraries don't mutate things i pass to them
21:19
<Bakkot>
/shrug
21:19
<rickbutton>
c) there is the -entire- benefit of immutable data structures over mutable ones
21:19
<Bakkot>
like I said, it's a correctness thing
21:20
<rickbutton>
it means that you don't need to reason about a whole class of problems, because they cannot happen
21:20
<devsnek>
rickbutton: no one is taking away the immutable structure
21:20
<Bakkot>
if records can contain mutable data, then yes, you are
21:20
<shu>
you're taking away the "deeply" part
21:20
<rickbutton>
yes, i should say, deeply
21:20
<devsnek>
only if you create that
21:20
<devsnek>
no one is making you create that
21:20
<shu>
no one's forcing you to mutate regular objects
21:20
<shu>
use those
21:20
<haxjs>
so if u don't want it mutate , don't send record with objects...
21:21
<devsnek>
i'm mostly interested in records for compound values
21:21
<devsnek>
not for the immutability
21:21
<shu>
then you are not the main target audience
21:21
<devsnek>
it can have multiple target audicnes
21:21
<shu>
yes, and tradeoffs were decided for the immutable audience
21:22
<devsnek>
as long as you can still have a deeply mutable record
21:22
<Bakkot>
devsnek "just be careful when writing it" means that you _do_ have to reason about this class of problems
21:22
<shu>
devsnek: that's not how the guarantees work
21:24
<devsnek>
i'm unconvinced but also not the majority so 🀷🏻
21:25
<shu>
you're unconvinced that's not how guarantees work?
21:25
<devsnek>
i'm unconvinced the guarantee is useful enough to warrant this design choice
21:25
<shu>
ah, then i daresay that is a pretty fringe opinion on the benefits of the deeply immutable guarantee
21:26
<devsnek>
i've used languages like rust that enforce immutability and people invent a lot of ways out
21:26
<devsnek>
refs boxes pointers etc
21:27
<shu>
it has a type system
21:27
<devsnek>
the type system says the reference is immutable
21:27
<devsnek>
but you can get a mut ref to one of the children
21:27
<devsnek>
same vein
21:27
<jridgewell>
I have no idea what this JSON.stringify example is doing. Why is serialization needed?
21:28
<devsnek>
i missed that
21:28
<jridgewell>
We already have the serializer arg to encode a `BigInt` into whatever you want.
21:28
<devsnek>
you don't return a string from that
21:28
<devsnek>
i don't think
21:29
<jridgewell>
You can return anything
21:29
<devsnek>
you can't return a bigint
21:29
<haxjs>
jridgewell i guess it allow u to deal with json generated by others.
21:29
<Bakkot>
you can't return a string of digits
21:29
<Bakkot>
or rather, you can return a string containing digits
21:29
<devsnek>
but it will be a string in the json
21:29
<Bakkot>
but not a sequence of digits which will appear in the json
21:29
<devsnek>
not a number type
21:29
<Bakkot>
yeah
21:29
<devsnek>
so you can't stringify bigints larger than 2**53
21:29
<ljharb>
jridgewell: for example, this proposal would have saved twitter a *ton* of engineering effort when tweet IDs hit 2**53
21:29
<haxjs>
some lib may output int64
21:30
<michaelficarra>
shu: JSON.parse with a reviver is performance sensitive?
21:30
<jridgewell>
https://www.irccloud.com/pastebin/Laqx6roR/stringify.js
21:30
<ljharb>
jridgewell: they had to add `id_str` next to `id` on every single API response, and input, rather than just providing a serializer/reviver
21:30
<shu>
michaelficarra: dunno, but JSON.parse in general is
21:30
<ljharb>
jridgewell: that's a string, not numeric digits
21:30
<shu>
michaelficarra: something worth verifying. this is asking for an extra allocation per reviver call
21:30
<ljharb>
jridgewell: when the (non-JS) server parses that, it will get a string and not an integer
21:30
<jridgewell>
You need an interpreter on both sides
21:31
<michaelficarra>
shu: I imagine most of the performance sensitive cases do not take a reviver
21:31
<ljharb>
no
21:31
<ljharb>
jridgewell: non-JS JSON parsers handle numbers larger than MAX_SAFE_INTEGER just fine, since json allows it
21:31
<devsnek>
jridgewell: right now the use case is a json numeric literal that is greater than a double
21:31
<ljharb>
jridgewell: it is *only* JS that can't handle the full range of json numbers
21:31
<haxjs>
string literal also useful
21:32
<Bakkot>
ljharb that's not true at all
21:32
<ljharb>
no?
21:32
<Bakkot>
very few languages can handle the full range of JSON numbers
21:32
<ljharb>
ah
21:32
<ljharb>
ok
21:32
<Bakkot>
most do not have arbitrary-precision floats
21:32
<jridgewell>
JSON numbers are infinite
21:32
<Bakkot>
even if they have bigints
21:32
<haxjs>
at least they can deal with int64
21:32
<ljharb>
well, for example, the apache thrift json code - that tons of things use - produces and accepts numbers that JS can't
21:32
<devsnek>
js is unique in that it can't do 64 bits
21:32
<devsnek>
well mostly unique
21:32
<ljharb>
so all of twitter's non-JS stack could handle tweet IDs except JS
21:33
<devsnek>
discord uses snowflakes but it makes them strings
21:35
<devsnek>
michaelficarra: you're not convinced of the need for serialization?
21:35
<devsnek>
ah nvm
21:37
<michaelficarra>
the need to create arbitrary JSON
21:37
<michaelficarra>
there is not need, and if you do need that, you probably don't want to be using JSON.stringify
21:40
<devsnek>
arbitrary module namespace identifiers are cool and we should advance to stage 4
21:42
<michaelficarra>
allowing a "*default*" export that's not the default export breaks the Shift AST :-(
21:43
<rickbutton>
devsnek: +1
21:44
<devsnek>
michaelficarra: *default* is runtime right? how did that leak into shift ast πŸ‘€
21:45
<ljharb>
bradleymeck: the prose at the top of the spec says utf8
21:46
<Bakkot>
devsnek function declarations require a binding identifier, binding identifier for `export default function (){}` is `*binding*`
21:46
<bradleymeck>
ljharb: doh
21:46
<Bakkot>
(not a choice I'm all that thrilled about)
21:47
<michaelficarra>
yeah I don't love that we did it, but it was following spec
21:47
<devsnek>
Bakkot: that's at runtime though
21:47
<devsnek>
in the ast that's `export default HoistableDeclaration[+Default]`
21:47
<devsnek>
it has no bindingidentifier in the ast
21:47
<michaelficarra>
devnsek: when we parse, we put a synthesised one
21:47
<Bakkot>
yeah, we could have said function decls don't require a bindingidentifier, but that would be painful
21:47
<Bakkot>
because almost all of them do
21:47
<michaelficarra>
to avoid making the BindingIdentifier of the FunctionDeclaration optional
21:48
<devsnek>
yeah my point was make the BindingIdentifier nullable
21:48
<devsnek>
just like the spec grammar
21:48
<devsnek>
fair enough though
21:48
<michaelficarra>
I think I still prefer "*default*" over optional BindingIdentifier of FunctionDeclaration
21:49
<Bakkot>
yeah
21:49
<Bakkot>
alternative is to make an explicit ExportFunctionDeclaration type or whatever
21:49
<devsnek>
i very much dislike putting a synthetic identifier
21:49
<michaelficarra>
devsnek: you ready to present Function#toString PR?
21:49
<devsnek>
i can be
21:49
<Bakkot>
spec also puts a synthetic name, just in a differnet place
21:50
<michaelficarra>
devsnek: you're scheduled to be next
21:50
<devsnek>
michaelficarra: yeah saw that
21:50
<devsnek>
thx
21:54
<michaelficarra>
can the chairs advance the TCQ topic so I can add a reply?
21:56
<devsnek>
we still have four minutes to get monads into the language
21:57
<bradleymeck>
michaelficarra: if you have any desires for how you want me to refactor spec feel free to just bother me
21:57
<shu>
number destructuring is a really top-notch proposal, not convinced it's a wrong answer
21:57
<benjamn>
https://twitter.com/littledan/status/1285816792796061701
21:58
<ljharb>
shu: core-js 2 did it, it broke a lot of things
21:58
<shu>
what
21:58
<ljharb>
shu: oh sorry that was iterable numbers, nvm
21:58
<shu>
yeah i'm talking about syntax
21:59
<michaelficarra>
bradleymeck: like… all of ecma262?
22:00
<bradleymeck>
for *default*
22:01
<bradleymeck>
since we gotta stop using it for [[ImportName]] and [[ExportName]]
22:37
<jridgewell>
Could just use a [[Type]] enum?