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? |