00:04 | <shu> | ljharb: so about https://github.com/tc39/proposal-atomics-wait-async/issues/28 |
00:05 | <shu> | ljharb: jridgewell has now convinced me in the status quo, namely that validation errors *should* return early; trigger finger was too quick on making the agenda PR. i imagine no issues with deleting the item without a PR |
00:05 | <ljharb> | no, there'd be no issues with that, but i'm not sure if that's true |
00:06 | <ljharb> | axel's article is fine advice for userland, and something i tend to follow as well, but the explicit decision (after that was posted) for `async function` was that it's zalgo to have sync errors from an async function |
00:06 | <ljharb> | eg, it should be impossible for an `async function` to synchronously throw under any circumstances |
00:07 | <ljharb> | also `Promise.all()` rejects, it does not throw |
00:07 | <ljharb> | that's a pretty clear precedent for "all errors of any kind only ever reject" |
00:08 | <shu> | justin has graciously provided counterexamples that it does |
00:08 | <shu> | Promise.all.call({}, [1, 2, 3]) // => Uncaught TypeError: #<Object> is not a constructor |
00:08 | <ljharb> | hm |
00:09 | <ljharb> | it's inconsistent with the ES6 ones, true |
00:09 | <shu> | you may argue that it's somehow a categorically different kind of validation error |
00:09 | <ljharb> | i'd say it kind of is |
00:09 | <shu> | i'd say i disagree :) |
00:09 | <ljharb> | in this case tho, it's because it can't know what kind of promise to create without a constructor |
00:11 | <shu> | OTOH webidl agrees with you and disagrees with me |
00:11 | <ljharb> | it seems strange if `waitAsync` can't be implemented with an `async function`. |
00:15 | <shu> | i don't understand what that means |
00:15 | <shu> | like you can't self-host it with an async function? |
00:17 | <shu> | (why is that weird? i'm a novice at actual software engineering with async features) |
00:17 | <ljharb> | yeah that's what i meant |
00:17 | <ljharb> | i think it's weird if a promise-returning function can't be self-hosted with the syntax for a promise-returning function. |
00:18 | <shu> | i see |
00:18 | <shu> | right, ok |
00:20 | <shu> | ljharb: okay, i need some time to digest. i don't really have skin in the game, so i'm kind of see-sawing |
00:20 | <ljharb> | fair enough |
00:20 | <shu> | i can see where the promise subclassing error is just categorically different |
00:20 | <shu> | and that since webidl as a matter of course converts all exceptions to rejected promises |
00:20 | <shu> | alignment with that seems most useful |
00:25 | <jridgewell> | I think following web API's practices would be good |
00:25 | <jridgewell> | My position was a weakly held "promise returning functions _could_ sync throw" |
00:26 | <jridgewell> | But looking through every example I could think of, the only ones I could find were the Promise constructor's static methods |
00:26 | <jridgewell> | I'd accept that's a 1-off exception :drum |
00:26 | <jridgewell> | I'd accept that's a 1-off exception :drum: |
00:26 | <jridgewell> | Agh, my pun is ruined. |
00:29 | <devsnek> | id say the errors should be async |
00:30 | <devsnek> | they're validating the arguments right |
00:30 | <devsnek> | I can maybe just barely see the argument for sync throw with the receiver being invalid |
00:30 | <devsnek> | but I'd rather that was async too |
00:33 | <Bakkot> | yeah, my impression was definitely that the webidl convention is pretty universal in modern js |
00:34 | <Bakkot> | it's also what node does, at least for `fs.promises` |
00:35 | <Bakkot> | if we were to deviate here I would expect the justification to be something specific to atomics, but I don't think there is such a reason |
00:38 | <Bakkot> | btw, in case anyone didn't get the "zalgo" reference, it's https://blog.izs.me/2013/08/designing-apis-for-asynchrony |
00:40 | <devsnek> | Facebook apparently replaces promises with fake promises that can resolve without waiting a tick |
01:06 | <shu> | great, thanks for all the datapoints |
01:07 | <shu> | will turn the errors async and ask for consensus for it |
01:07 | <shu> | incidentally how do folks feel about a Promise introspection API for the power uses that want to do something synchronously with Promises? |
01:08 | <shu> | domenic pointed me to a previous iteration: https://github.com/jamiebuilds/proposal-promise-prototype-inspect |
01:08 | <devsnek> | so much nope |
01:09 | <devsnek> | the demos are like literally the antithesis of the design of promises |
01:10 | <shu> | that promises being non-introspectable is central to its design? |
01:10 | <shu> | i missed that |
01:10 | <devsnek> | zalgo again |
01:11 | <ljharb> | yes, it shouldn’t be possible to observe the result synchronously |
01:11 | <devsnek> | if you want to do that just don't use promises |
01:11 | <shu> | so it's a slippery slope argument, that it'll enable that use case? |
01:11 | <devsnek> | is there any other use case |
01:12 | <devsnek> | the repo seems entirely focused on "the promise is fulfilled but you don't want to wait a tick to get the result" |
01:12 | <shu> | well, the current design of Atomics.waitAsync misses an optimization opportunity because mixing sync and async is a no go, and introspecting promises synchronously is no go |
01:12 | <shu> | if there were a way to do the latter, if that's somehow more palatable than the former, that'd re-enable that optimization opportunity |
01:12 | <devsnek> | optimization where you don't have to wait a tick? |
01:13 | <shu> | right |
01:13 | <Bakkot> | what's the optimization? |
01:13 | <devsnek> | right the design of promises is that you always have to wait a tick |
01:13 | <shu> | https://github.com/tc39/proposal-atomics-wait-async/blob/master/SYNC-RESOLVE.md |
01:13 | <shu> | basically, the point of the wait API is a building block for efficient mutexes in userland |
01:14 | <devsnek> | waitNonblocking seems fine to me |
01:14 | <devsnek> | like i said, the solution is to not use promises |
01:14 | <shu> | it... can't not use promises |
01:14 | <Bakkot> | devsnek: waitNonBlocking returns a promise |
01:14 | <Bakkot> | that is the whole point of it |
01:14 | <devsnek> | oh |
01:14 | <shu> | also the name is out of date, it's waitAsync now sorry |
01:14 | <devsnek> | well |
01:14 | <shu> | but anyway the point of the API is a conditional wait |
01:14 | <devsnek> | Atomics.tryWait |
01:14 | <devsnek> | or something |
01:14 | <shu> | i'm not sure what that means |
01:15 | <devsnek> | it exits out if it would block |
01:15 | <devsnek> | and then you can go do other things or call the waitAsync function |
01:15 | <shu> | ah but whether an agent can block or not is a static property of the agent. the main thread can't block |
01:15 | <shu> | it's not that the user is choosing to block or to get a promise, it's that there's no choice |
01:16 | <Bakkot> | can you not just do Atomics.read, and then a compare, and then Atomics.waitAsync? |
01:16 | <devsnek> | it somehow doesn't always return a promise |
01:16 | <shu> | Bakkot: yep, that's the workaround at the end |
01:16 | <Bakkot> | oh, cool |
01:16 | <Bakkot> | that seems fine to me in honesty; I would not expect that to be a significant performance difference |
01:16 | <shu> | Bakkot: that's probably just fine in practice 99% of the time. there's a TOTCTOU problem so you might get really surprising degraded mutex performance once on a blue moon |
01:16 | <shu> | Bakkot: agreed, i don't plan to change it, just got to thinking about it again |
01:17 | <shu> | the weirdness is mainly that it *feels* weird to have a conditional wait API that's supposed to "fail fast" and signal when you don't need to wait, but for the async version, you can't observe that signal until the microtask checkpoint |
01:17 | <shu> | at which point, you'd probably already waited a while |
01:18 | <devsnek> | i guess its worse for the web since it has the big render cycle thing to deal with |
01:18 | <Bakkot> | the TOTCTOU problem is there for atomics.waitAsync too, yeah? nothing prevents the value from changing out from under you immediately as soon as the promise resolves, before you have had time to read from it? |
01:18 | <Bakkot> | shu anyway the alternative is, you return either `{ fast: true, value: value }` or `{fast: false, promise: promise }` |
01:18 | <Bakkot> | and then the user switches on `fast`, and awaits the promise if they got one |
01:18 | <shu> | Bakkot: ah good point, the problem is there for the entered-the-wait-queue path as well |
01:19 | <shu> | but i don't think in practice, once a thread is woken |
01:19 | <shu> | you care about what the value of the futex location is |
01:19 | <shu> | Bakkot: yeah, that also feels kinda gross, so status quo seems all right for now |
01:19 | <Bakkot> | once atomics.read has returned the right value, you also don't care, right? |
01:20 | <shu> | you do, because you want the "the read value is the right one, now enter the wait queue" to be an atomic action |
01:20 | <shu> | in case there's a lot of contention, for instance |
01:20 | <Bakkot> | if the value read is the right one, why are you entering the queue? |
01:20 | <shu> | that's the futex api, you enter the wait queue when *addr == val |
01:21 | <Bakkot> | wait ok correction. why is the thing you would do after Atomics.waitAsync has fulfilled successfully any different from the thing you would do after Atomics.read has returned the value you were looking for? |
01:22 | <Bakkot> | (it has been a while since I did low-level multithreading, sorry) |
01:22 | <devsnek> | because the thing you do next is entering the wait queue |
01:22 | <devsnek> | which should be atomic |
01:22 | <devsnek> | oh after it fulfills |
01:22 | <devsnek> | nvm |
01:22 | <shu> | Bakkot: the canonical example is something like, you have a tri-state mutex: 0 == unlocked, 1 == locked, 2 == locked and contended |
01:22 | <shu> | Bakkot: if there's contention, you don't want to immediately block the thread and wait, you want to do that if there's contention |
01:23 | <Bakkot> | that last message has a typo presumably, both of your branches are "there's contention" |
01:23 | <shu> | oops, if there's *no* contention you don't want to immediately block |
01:24 | <devsnek> | sounds like we need futures |
01:24 | <shu> | Bakkot: so if the state is != 0, you wait only if the state is already == 2, or successfully compxchg the state from 1 to 2 |
01:24 | <shu> | otherwise, sometimes it gets "fast unlocked" in the interim and you just acquire the lock |
01:25 | <shu> | so the futex wait call is on that state == 2 |
01:27 | <shu> | does the TOTCTOU make sense now? |
01:27 | <Bakkot> | still thinking through it |
01:27 | <shu> | Bakkot: https://eli.thegreenplace.net/2018/basics-of-futexes/ |
01:27 | <shu> | search for "simple mutex" |
01:27 | <shu> | there's a nice commented code snippet |
01:30 | <Bakkot> | this snippet seems like you could implement it just fine using Atomics.compareExchange + the promise-based Atomics.waitAsync (in place of the sleep) |
01:31 | <Bakkot> | I guess the point is that you want to return early if it has been unlocked _between_ your compare-exchange and your call to waitAsync? |
01:32 | <Bakkot> | this being the "fast unlock" thing |
01:32 | <shu> | right |
01:32 | <shu> | where the cmoment says "Note that it's not necessary to loop around this syscall" |
01:32 | <shu> | the loop in JS land with waitAsync is waiting until the microtask checkpoint |
01:32 | <shu> | which... might be short, might be long, who knows? app dependent |
01:35 | <Bakkot> | If the syscall was guarded on `if (Atomics.read(atom) != 0) {`, would that not do the thing you want? |
01:35 | shu | thinks |
01:37 | <shu> | no, i don't think so, you can imagine some scheduler that executes that line |
01:37 | <shu> | then basically parks that thread |
01:38 | <shu> | in the meantime 2 other threads do their locking/unlocking thing, and by the time you resume the first thread, you have 0 again, right? |
01:39 | <Bakkot> | ah |
01:39 | <Bakkot> | yeah |
01:39 | <Bakkot> | ok |
01:39 | <shu> | i don't think it's possible to fix in userland |
01:39 | <shu> | but the chances of failure are exceedingly small |
01:39 | <shu> | and "failure" just means extra long to acquire a lock, not deadlock or anything |
01:42 | <Bakkot> | yeah |
01:43 | <Bakkot> | and the case it comes up is specifically where someone has unlocked your futex between the time you looked and the time you went to enter the queue, which is pretty much the immediate next operation you are doing |
01:43 | <Bakkot> | having the extra microtask tick on the main thread in that case seems ok |
01:45 | <Bakkot> | this is definitely an Atomics-specific reason to be able to return early sometimes, though; if we wanted to address it I think having your API return sometimes-sync sometimes-promise (ideally wrapped, as in my example above) would be a better fix than introducing a general-purpose promise introspection utility |
01:48 | <shu> | right |
01:48 | <shu> | Bakkot: yeah, that sounds very reasonable |
01:49 | <shu> | need to stew on whether we want to return a wrapped thing |
01:49 | <shu> | it's kinda unergonomic to not be able to write `await Atomics.waitAsync(...)` but shrug |
01:50 | <Bakkot> | hmm, yeah |
01:50 | <Bakkot> | I guess it would be ok for it to not be wrapped, given that await can be passed a non-promise value |
01:50 | <Bakkot> | I dislike that fact but it is what it is |
01:51 | <Bakkot> | though, actually, does that `await` not introduce an additional microtask tick in the case that waitAsync returns a promise? |
01:52 | <devsnek> | it doesn't |
01:52 | <devsnek> | though it used to |
01:52 | <Bakkot> | ahh |
01:52 | <Bakkot> | cool |
01:52 | <devsnek> | that was the "await optimization" thing the chakracore people brought at some point |
01:52 | <devsnek> | changed it to just do Promise.resolve(v).then(resume) |
01:54 | <jridgewell> | The await optimization took it from 3 ticks to 1 tick. |
01:54 | <jridgewell> | But using `await` always ticks. |
01:55 | <jridgewell> | The only way a sync-promise would work is if you did `Atomics.waitAsync(...).then(syncStuff)` |
02:03 | <Bakkot> | shu: I guess I am convinced that a not-wrapped value would be OK. I would maybe prefer `{ fast: true, value: 0 }` or `{ fast: false, value: promise }`, so that you don't have to do the typeof check and can still do the handy `await Atomics.waitAsync().value`. |
02:03 | <Bakkot> | but, also, I don't think this necessarily needs solving |
03:07 | <shu> | i think wrapped is the way to go if we wanna solve it, but yeah i agree i’m not sure this needs solving |
17:55 | <TabAtkins> | Btw, I know I missed this meeting's deadline (not thru laziness! we just came up with the idea too late!), but putting this out there in prep for the next meeting: https://github.com/tabatkins/proposal-item-method |
17:57 | <devsnek> | hopefully we can dedicate 30-40m to bikeshedding the name |
17:58 | <Bakkot> | devsnek: fortunately the justification for this one is in many ways tied to the name being exactly `item` |
17:58 | <Bakkot> | which is quite nice |
17:59 | <devsnek> | why not at() |
17:59 | <Bakkot> | read the readme |
18:00 | <Bakkot> | tldr is "to match the DOM" |
18:00 | <shu> | i think we've been all right about bikeshedding recently |
18:00 | <devsnek> | we don't *have* to match the dom though |
18:00 | <shu> | we don't have to do a lot of things |
18:00 | <devsnek> | not that i disagree with the motivation |
18:01 | <Bakkot> | devsnek: we don't have to, but the advantage of matching is not just for consistency but that it allows the DOM array-wrapping apis to avoid some magic |
18:04 | <devsnek> | i like the proposal |
18:04 | <devsnek> | just wanted to point out it was technically not imperative |
18:04 | <Bakkot> | ah, yeah |
18:05 | <Bakkot> | yeah it's not imperative, just, there's a strong built-in bias towards one particular name, which helps avoid bikeshedding |
18:07 | <Bakkot> | TabAtkins: fwiw it looks like mootools does _not_ have `.item`; it was the largest offender on fragile builtin overrides pattern, to my knowledge. so that's hopeful. |
18:07 | <TabAtkins> | Nice. |
18:07 | <devsnek> | smooshIntoCollectionIndex(n) |
18:08 | <bradleymeck> | Prototype / Ext also had issues in the past (goes off to check) |
18:09 | <bradleymeck> | they both do not have that method |
18:12 | <TabAtkins> | double nice |
18:12 | <TabAtkins> | re: bikeshedding; I |
18:12 | <TabAtkins> | lol |
18:13 | <devsnek> | clearly we need to add a new syntax |
18:13 | <devsnek> | japanese quotation marks |
18:13 | <TabAtkins> | re: bikeshedding; I'm one of the people who've desperately wanted negative indexing as well, so *any* name would make me happy on those grounds, but yeah, using exactly .item() would make me even happier |
18:13 | <devsnek> | there was a proposal from someone at some point for indexing/stepping syntax |
18:13 | <devsnek> | might've been on the discourse |
18:14 | <bradleymeck> | API would be simpler / provides benefits that can be backported cheaper |
18:14 | <devsnek> | yeah i agree |
18:17 | <TabAtkins> | Bakkot/bradleymeck: thanks, added the info to the proposal |
18:19 | <TabAtkins> | btw, am I right in my findings that the TypedArray superclass isn't publicly exposed under any name, and so the way to add to all typed arrays' prototypes is indeed `Uint8Array.__proto__.prototype.item = item;`? |
18:20 | <bradleymeck> | TabAtkins: use Object.setPrototypeOf as Deno/Node don't guarantee __proto__ anymore, but yes |
18:21 | <bradleymeck> | getPrototypeOf* |
18:22 | <devsnek> | i wonder if we should have an Iterator.prototype.nth |
18:22 | <bradleymeck> | -1th |
18:22 | <devsnek> | probably not since we can't really lower it for perf |
18:22 | <devsnek> | but its nice in rust |
18:23 | <TabAtkins> | Anything that implies random access over an iterator is probably bad. |
18:24 | <TabAtkins> | You want to be more explicit that you're first discarding N-1 values from the iterator, then taking the next one. |
18:24 | <devsnek> | there was an interesting discussion along that line in the proposal |
18:24 | <devsnek> | about giving an index in the map/etc functions |
18:24 | <TabAtkins> | That can def be worthwhile to build into the Iterator protocol under an easy name, it just needs to be clear in its semantics. |
18:25 | <devsnek> | yeah rn you'd do skip(n).next() i guess |
18:25 | <TabAtkins> | yeah |
18:25 | <devsnek> | or skip(n - 1) i guess |
18:25 | <Bakkot> | seems fine to me |
18:25 | <TabAtkins> | no, skip(n) is correct |
18:25 | <Bakkot> | that's plenty explicit |
18:25 | <devsnek> | if you want the fifth item you'd skip four and take one |
18:26 | <TabAtkins> | where's the zeroth item? |
18:26 | <TabAtkins> | you filthy 1-indexer |
18:26 | <devsnek> | skip(0) is no-op |
18:26 | <TabAtkins> | exactly |
18:26 | <devsnek> | its the number of items to skip |
18:26 | <TabAtkins> | Yes, I know. |
18:26 | <TabAtkins> | I'm saying that ".nth(0)" is "skip 0 items, get the next one" |
18:27 | <devsnek> | oh i see what you mean |
18:27 | <TabAtkins> | because the 5th item is at index 5, and thus has five items before it |
18:27 | <devsnek> | yeah by fifth item i meant |
18:27 | <devsnek> | lol |
18:28 | <devsnek> | *violent agreement* |
19:00 | <shu> | what if it was named nst |
19:03 | <TabAtkins> | shu: what if you were named nst |
19:03 | <TabAtkins> | let's compromise and name is nnd |
19:03 | <shu> | n2d sgtm |
19:17 | <rkirsling> | `nnd` -- takes a number but rounds it to the nearest integer ending in a 2 first |
19:17 | <Bakkot> | proposal to add rounding mode flag to Math.round |
19:20 | <shu> | oh wow |
19:20 | <shu> | https://upload.wikimedia.org/wikipedia/commons/8/8a/Comparison_rounding_graphs_SMIL.svg |
19:20 | <shu> | what a delightful chart |
19:21 | <Bakkot> | this svg has hover effects |
19:21 | <Bakkot> | I did not know you could do that |
19:22 | <shu> | neither did i |
19:29 | <TabAtkins> | Oh yeah, hover is great |
19:29 | <TabAtkins> | I don't think *this chart* uses them very well; it's still nearly unreadable when you hover one, but hey |
19:34 | <shu> | i'm impressed by the number of things and the different colors |
19:47 | <ljharb> | (node still guarantees __proto__ by default, what flags do is different) |
19:48 | <ljharb> | TabAtkins: so, aside from the DOM pushing a specific name, what semantics does that push for, specifically around validation errors/exceptions and edge cases? |
19:49 | <TabAtkins> | That's a good question and I'll figure it out and document it |
19:50 | <ljharb> | TabAtkins: because i'm only mildly annoyed by the web predetermining the name, but if they predetermine other semantics that don't align with what would actually be conventional for JS, i'm a lot more annoyed |
19:50 | <TabAtkins> | I highly suspect that the more detailed semantics aren't important to compat here |
19:52 | <ljharb> | that's ideal :-) |
19:54 | <shu> | ljharb: so i think a uniform way to do relative indexing for indexable data is the high-order bit; if the web compat bit cost is naming, that sounds pretty win-win to me |
19:57 | <Bakkot> | TabAtkins: NodeList's item does not support negative indexes |
19:58 | <Bakkot> | changing that behavior seems like it might be breaking |
19:59 | <TabAtkins> | I doubt that. We'll see! |
19:59 | <TabAtkins> | Before this ships we'll probably need to instrument and verify. |
19:59 | <Bakkot> | if not that would be ideal |
20:00 | <TabAtkins> | Not hard to measure how many pages pass negative numbers to it right now |
20:01 | <Bakkot> | great |
20:02 | <Bakkot> | it is easy to imagine someone doing `i = list.length - 1; do { e = list.item(i); doThing(e); --i; } while (e != null)` or whatever |
20:02 | <Bakkot> | people write all sorts of crazy loops |
20:02 | <shu> | what does NodeList's item do now when passed a negative number? |
20:02 | <Bakkot> | returns `null` |
20:03 | <Bakkot> | (nb not `undefined`, which is probably also something which would have to change) |
20:09 | <shu> | ah |
21:03 | <ljharb> | shu: oh sure, for naming that's why i'm only mildly annoyed |
21:03 | <ljharb> | (fwiw i do consider the feature useless without support for negative numbers) |
21:07 | <TabAtkins> | Yeah, DOM stuff always returns null, another Java legacy. |
21:08 | <TabAtkins> | But since most people test for null with either a `== null` or `!` check, switching to undefined has a good chance of being safe. |
21:09 | <devsnek> | honestly this seems like arguments to not use `.items` |
21:09 | <devsnek> | er `.item` |
21:10 | <TabAtkins> | Unless they prove to be breaking, they're not. And the point of me introducing this is to try and get .item() specifically. |
21:10 | <TabAtkins> | In the worst case, we just don't upgrade the legacy interfaces. |
21:11 | <TabAtkins> | And JS is free to do whatever. |
21:11 | <devsnek> | i mean you'd have to prove no code does `=== null` |
21:11 | <devsnek> | or yeah we can not make the web use the new behaviour |
21:11 | <TabAtkins> | No, we just have to have reasonable assurance, and not see reported breakage in dev/beta channels. |
21:12 | <devsnek> | seems like a lot of trouble for no payoff |
21:13 | <TabAtkins> | The payoff is I get to quit writing `[...document.querySelectorAll("a")].map(foo)` |
21:13 | <TabAtkins> | becasue NodeList is a freakin' Array now |
21:14 | <devsnek> | wait do you actually want to replace NodeList with Array |
21:14 | <TabAtkins> | (Or rather today I always do a `function findAll(sel) { return [...document.querySelectorAll(sel)];}` at the top of my projects |
21:14 | <TabAtkins> | No, with ObservableArray, which is a proxy around Array that lets us still intercept get/set/etc. |
21:14 | <TabAtkins> | Domenic wrote it up and got it into WebIDL just a little bit ago. |
21:14 | <Bakkot> | I have never seen someone spell that function anything other than `$$` |
21:16 | <devsnek> | so you want it to be called .item so you don't have to worry about whether you're dealing with an ObservableArray or an Array |
21:16 | <devsnek> | or something else |
21:22 | <ljharb> | TabAtkins: you should already be writing `Array.from(document.querySelectorAll('a'), foo)` :-p |
21:23 | <Bakkot> | god why |
21:23 | <ljharb> | so you don't create an intermediate array |
21:23 | <ljharb> | Array.from's mapper function is a godsend, i use it allllll the time |
21:23 | <TabAtkins> | devsnek: ObservableArray is a proxy over Array, so it's not a matter of "which one you see" - as far as the author is concerned they're getting an Array. |
21:23 | <Bakkot> | something something premature optimization |
21:23 | <devsnek> | TabAtkins: well in theory ObservableArray could be a subclass and a proxy |
21:24 | <TabAtkins> | see the doc for exploration of some of the alternate choices and why i prefer not to have them |
21:24 | <ljharb> | the iterator protocol is slow ¯\_(ツ)_/¯ |
21:24 | <devsnek> | not just a proxy |
21:24 | <TabAtkins> | including that one in particular |
21:24 | <ljharb> | Bakkot: but yeah fair |
21:24 | <Bakkot> | ljharb for arrays? is it really? |
21:24 | <ljharb> | Bakkot: NodeList isn't an array |
21:24 | <TabAtkins> | yah it's fakey fake fake |
21:24 | <Bakkot> | s/arrays/nodelists/ |
21:24 | <ljharb> | Bakkot: i'm sure it's optimized for the common use cases in modern engines, the ones where perf matters the least |
21:25 | <devsnek> | i wouldn't describe iterators as slow |
21:25 | <ljharb> | what i also personally like is, not having to rely on `.map` being there |
21:25 | <devsnek> | but i wouldn't describe them as blistering fast either |
21:27 | <Bakkot> | ljharb yeah I mean if you observe that you have performance issues I am all for using whatever hacks are necessary to become fast, though usually that means using imperative loops rather than function style |
21:27 | <Bakkot> | if you have not yet observed that, keep your code readable |
21:28 | <devsnek> | https://twitter.com/devsnek/status/1243586726976724992 |
21:31 | <rkirsling> | lul |
21:32 | <ljharb> | Bakkot: i find the array.from example readable personally |
21:32 | <ljharb> | devsnek: lol |
21:32 | <devsnek> | readable because you know the signature of Array.from |
21:33 | <rkirsling> | I didn't know about that second arg |
21:33 | <devsnek> | TabAtkins: your site is making me dizzy |
21:33 | <TabAtkins> | the homepage? |
21:33 | <devsnek> | ya lol |
21:33 | <TabAtkins> | lol |
21:34 | <devsnek> | my website is literally text/plain though so i can't really throw shade |
21:34 | <Bakkot> | ljharb it's fine if you and all future readers of the code can be expected to know about the second argument, but I don't think people should know about it. people should learn about `Array.from()` with one argument, and learn about `map`, and those compose naturally, and then there is no reason to ever learn this third thing (Array.from takes a second argument). |
21:35 | <Bakkot> | (unless they start getting into microoptimization, of course, but that's gonna need to be backed up with bechmark data for their particular codebase and userbase) |
21:35 | <ljharb> | i don't think it's unreasonable to expect people to know about something built into the language, that's not esoteric |
21:35 | <Bakkot> | it should be esoteric |
21:35 | <Bakkot> | because there is no reason to learn it |
21:36 | <devsnek> | in theory an engine can combine Array.from().map |
21:36 | <devsnek> | if the map argument is pure at least |
21:36 | <ljharb> | if there's no reason to learn it, then why was it shipped |
21:36 | <Bakkot> | ljharb do you also use the second argument to Array.p.map? |
21:36 | <ljharb> | devsnek: only if the mapper doesn't access or detect the third argument |
21:37 | <devsnek> | like i said, if its pure |
21:37 | <shu> | we ship plenty of things we don't want people to learn, what |
21:37 | <ljharb> | devsnek: it can be pure even if it accesses it |
21:37 | <Bakkot> | do you also think it is reasonable to expect them to learn that? |
21:37 | <ljharb> | Bakkot: no, true enough |
21:38 | <devsnek> | shu: are you telling me i should've bother learning String.prototype.blink |
21:38 | <shu> | there's a lot of reason to learn that over second argument to map or from |
21:38 | <shu> | - street cred |
21:38 | <TabAtkins> | ...what's the second argument to map() |
21:38 | <ljharb> | ok but the `this` argument to .map was added in 2009, before arrows, and before .bind was common |
21:38 | <devsnek> | thisArg |
21:38 | <shu> | - historian cred |
21:38 | <TabAtkins> | oh, to the map callback |
21:38 | <TabAtkins> | phew |
21:38 | <ljharb> | Array.from's mapper arg was added in 2015, along with array spread |
21:39 | <TabAtkins> | oh no wait i see |
21:39 | <ljharb> | so what was the rationale for it, if it's not worth learning? |
21:39 | <devsnek> | TabAtkins: and the callback itself takes (item, index, array) |
21:39 | <TabAtkins> | i think you mean (element, index, collection) (EIC, still waiting for "haltToken") |
21:39 | <devsnek> | lol |
21:39 | <shu> | can confirm that's how brendan himself teaches it |
21:39 | <devsnek> | `(element, index, collection, signal)` |
21:40 | <jridgewell> | I don't understand how you could replace these not-an-arrays with `ObservableArray` |
21:40 | <devsnek> | well if it only breaks less than .0003% of the web or whatever chrome's metric is |
21:40 | <jridgewell> | We'd have to add every method that the no-an-array has to `ObservableArray` or `Array`? |
21:41 | <TabAtkins> | jridgewell: There's precisely one. |
21:41 | <TabAtkins> | .item() |
21:41 | <jridgewell> | For all the not-an-arrays? |
21:41 | <TabAtkins> | For a lot of them, at least |
21:41 | <TabAtkins> | NodeList, StyleSheetList |
21:41 | <shu> | ljharb: the reason for the mapper function for Array#from was it enables easier array subclassing |
21:41 | <Bakkot> | (ugh) |
21:41 | <jridgewell> | Ok. |
21:42 | <ljharb> | shu: how? |
21:42 | <rkirsling> | (does this mean `e, i, c, h` is the `s t a b` of JavaScript) |
21:42 | <jridgewell> | So because `Array` would now conform the the API provided by `NodeList`, it wouldn't matter that we now returned an array... |
21:42 | <ljharb> | shu: you mean like a subclass doesn't have to override `.map`, just `static from`? |
21:42 | <jridgewell> | Would there still be the perf cliff? |
21:42 | <jridgewell> | Eg, a live `NodeList` |
21:42 | <TabAtkins> | jridgewell: I mean, we still need the proxy for things that are live. |
21:42 | <shu> | ljharb: https://github.com/tc39/notes/blob/master/meetings/2013-01/jan-30.md#revising-the-array-subclassing-kind-issue |
21:43 | <ljharb> | shu: lol that says there's a thisArg, but there isn't one |
21:43 | <ljharb> | but well, now i feel dirty, because one of my favorite parts of the language was created to enable a dumb and inconsistent subclassing model |
21:44 | <shu> | no i think the lesson here is just all artifacts exist historically |
21:44 | <ljharb> | fair |
21:44 | <ljharb> | i retract my implication that existence proves usefulness |
21:44 | <ljharb> | (but i still find this one useful) |
21:45 | <TabAtkins> | rkirsling: yes |
21:45 | <shu> | yeah i think for computers especially after some time the original motivations don't really matter |
21:46 | <devsnek> | > existence proves usefulness *cries in abstract equality* |
21:46 | <rkirsling> | TabAtkins: :D |
21:46 | <TabAtkins> | (i still dont' understand what the `s t a b` mean, either individually or collectively) |
21:46 | <rkirsling> | it's `s t` and `a b` separately but adjacent |
21:46 | <rkirsling> | which comically forms a word |
21:46 | <TabAtkins> | if you try to explain Lenses to me here in IRC i will fight you |
21:47 | <rkirsling> | I absolutely will not 😂 I actually only understand it at a very high level myself |
21:48 | <TabAtkins> | yeah i know roughly how to use it, but no clue whatsoever how the abstraction works |
21:48 | <rkirsling> | i.e. "pure FP decided it wanted in on the property access thing too" |
21:48 | <TabAtkins> | which annoys me becasue people complain about monads and, like, they're trivial, so i'm not sure if lenses are in the same boat and i haven't hit the insight yet, or they're actually complicated and it's okay |
21:49 | <shu> | that kind of functional programming is basically that kindergarten game where you need to find the right shaped hole for objects |
21:49 | <shu> | and then being very proud you stacked several objects together and put it into a novel shaped hole |
21:50 | <rkirsling> | isn't that kind of the whole idea of naturality in category theory though? :p |
21:50 | <TabAtkins> | basically yeah |
21:51 | <devsnek> | someone was really upset that we put in optional chaining instead of a method on properties that returns Option |
21:51 | <Bakkot> | took me an embarrassingly long time to figure out you were talking about lenses and not pokemon |
21:51 | <shu> | as much as i dunk on pokemon, pokemon is orders of magnitude more beneficial for society than category theory |
21:51 | <Bakkot> | ( https://bulbapedia.bulbagarden.net/wiki/Same-type_attack_bonus ) |
21:51 | <rkirsling> | shu: ouch |
21:52 | <rkirsling> | Bakkot: fascinating |
21:54 | <rkirsling> | that Baez guy is all about applying category theory to save the planet though |
21:54 | <rkirsling> | here's hoping he finds success 🍷 |
21:57 | <shu> | i think mathematicians are exempt from my hot take |
21:57 | <rkirsling> | 😆 |
22:32 | <Bakkot> | agendas repo seems like it should maybe be read-only for non delegates, if that is a possibility? |
22:32 | <rkirsling> | +1 |
22:34 | <ljharb> | is it not? |
22:35 | <Bakkot> | evidently not, no |
22:35 | <devsnek> | i wasn't able to modify it until i became a delegate |
22:35 | <devsnek> | i was in the invited experts team before that |
22:35 | <Bakkot> | oh, sorry, I meant issues as well as the code |
22:35 | <ljharb> | only for a 24 hour period as a time |
22:35 | <ljharb> | all issues are open for all on github |
22:35 | <devsnek> | huh |
22:36 | <Bakkot> | bleh |
22:55 | <rkirsling> | :( |
23:16 | <rkirsling> | Bakkot: you're saying "must implement as specified" is weaker than "must not implement except as specified"? like, the former would still allow for bonus params or something? |
23:17 | <rkirsling> | er sorry "must not extend except as specified" |
23:17 | <Bakkot> | rkirsling: yes |
23:17 | <rkirsling> | I see |
23:17 | <Bakkot> | and also in the case that 402 is not active it says "the following is used", rather than "the following must be used" or whatever |
23:18 | <rkirsling> | wait but "is" sounds even more unwavering to me than "must" |
23:21 | <Bakkot> | sorry, the "it" in that sentence is "BigInt.prototype.toLocaleString" |
23:21 | <Bakkot> | so, yes, that's my point - the current BigInt.prototype.toLocaleString does not read as a strong of a requirement as the forbidden extensions does |
23:24 | <rkirsling> | alright |
23:25 | <rkirsling> | I see |
23:26 | <rkirsling> | do you agree that it seems like an unintentional omission though? |
23:27 | <devsnek> | can't we just say "extensions to builtins that are otherwise specified in 402 are forbidden" |
23:27 | <devsnek> | or something along those lines |
23:27 | <devsnek> | bikeshedding needed |
23:28 | <rkirsling> | yeah it might be ideal to not have to keep maintaining the list |
23:30 | <ljharb> | it seems like a spec bug, but still a normative change |
23:30 | <ljharb> | i also think it would be good to avoid the list |
23:30 | <ljharb> | (especially since you can already grep for "402" in the spec to find all those extension points) |
23:31 | <Bakkot> | rkirsling yes it's definitely accidental; this is just a process point |
23:32 | <Bakkot> | we can resolve it in two minutes in plenary |
23:37 | <rkirsling> | cool |
23:39 | <TabAtkins> | Bakkot: Added a list of upgradeable interfaces, and a (comprehensive afaict) list of the changes from current behavior with an exploration of the possible breakages that could result. |
23:39 | <TabAtkins> | thanks for the push to do it |
23:39 | <Bakkot> | nice! |