| 14:56 | <Chris de Almeida> | meeting start is imminent! |
| 15:02 | <Michael Ficarra> | oooh lower latency on the transcription will be great! |
| 15:03 | <bakkot> | I have no idea why that would be |
| 15:04 | <bakkot> | unless it's like, yesterday google docs was slow, which I could totally believe |
| 15:08 | <Chris de Almeida> | it is noticeably better than last meeting |
| 15:08 | <Chris de Almeida> | just my observation 🤷 |
| 15:10 | <Justin Ridgewell> | I don't think I need set.getAndDelete(), but have wanted set.getAndInsert(): boolean a few times |
| 15:11 | <Justin Ridgewell> | To both check if it's currently in, and insert it if it's not. |
| 15:11 | <Justin Ridgewell> | Mainly to guard if (!set.getAndInsert()) { expensive() } |
| 15:12 | <Justin Ridgewell> | Really add() should have returned the current boolean status all along… |
| 15:17 | <nicolo-ribaudo> | What if we had a special flag on objects to make them falsish, so that the Map returned by .add() turns into falsish for the first subsequent boolean check if the value was already there 🧠 |
| 15:18 | <rbuckton> | Really tryAdd |
| 15:19 | <Justin Ridgewell> | That works too |
| 15:20 | <bakkot> | just do your side effects in getOrInsertComputed |
| 15:21 | <bakkot> | set.getOrInsert(v, () => { expensive(); return newValue }) |
| 15:21 | <Justin Ridgewell> | That's only Map, right? |
| 15:21 | <ljharb> | for take, what's the proposal name at stage 1 (that describes the problem statement)? |
| 15:21 | <bakkot> | oh, sure |
| 15:21 | <Olivier Flückiger> | nicolo-ribaudo: to support this also for the declaration in conditionals it would have to be passed through assignments though |
| 15:24 | <Michael Ficarra> | parseFrozen sounds like I need to pass Object.freeze(Object("{ ... }")) |
| 15:24 | <Michael Ficarra> | like somehow JSON.parse will mutate its input |
| 15:24 | <Michael Ficarra> | that said, I still think it's better than using an option for this |
| 15:25 | <ljharb> | cc Devin Rousso |
| 15:28 | <Devin Rousso> | im not sure what you mean? 😅 |
| 15:28 | <Devin Rousso> | i think getAndDelete was what folks liked |
| 15:28 | <ljharb> | that's the method name. but what's the problem it's solving? stage 1 is about a problem statement, not a solution/API |
| 15:29 | <Michael Ficarra> | that's also the problem it's solving, which is why we suggested it as the method name |
| 15:29 | <Devin Rousso> | oh! gotcha i understand what you're asking |
| 15:29 | <Devin Rousso> | it's being able to in a single operation "take" things out of Map |
| 15:30 | <Devin Rousso> | i.e. get the value for a given key AND delete the key at the same time |
| 15:30 | <ljharb> | i'm looking for something concise to title the proposal in the proposals repo :-) |
| 15:30 | <Michael Ficarra> | agh, Arrays still having Array.prototype is unfortunate |
| 15:30 | <Devin Rousso> | i described it as "Read a value and remove it in the same step" in the proposal repo |
| 15:31 | <Michael Ficarra> | Map read and remove? |
| 15:31 | <bakkot> | primitives still have their prototypes too |
| 15:32 | <bakkot> | arrays we could conceivably make a FrozenArrayPrototype which is just a copy with all the same methods but frozen + null protot, but we can't do that for primitives |
| 15:33 | <ljharb> | SGTM |
| 15:33 | <ljharb> | please bounce the proposal repo into tc39-transfer and i'll take care of it :-) |
| 15:33 | <Michael Ficarra> | that said, the relevant ES terms (from Map.prototype) are already "get" and "delete", so I would personally go with those |
| 15:37 | <Justin Ridgewell> | Didn't we discuss packed arrays recently? As long as the array has no holes, it shouldn't matter that it has a prototype. |
| 15:40 | <Olivier Flückiger> | not if you read a named property on the array |
| 15:44 | <nicolo-ribaudo> | We could have options presets as static properties of JSON |
| 15:44 | <nicolo-ribaudo> | Like, JSON.parse(..., JSON.FROZEN_AND_NULL_PROTO) |
| 15:44 | <nicolo-ribaudo> | Well, with a shorter name than JSON.FROZEN_AND_NULL_PROTO |
| 15:44 | <nicolo-ribaudo> | Something that's much simpler to type than spelling out the options bag |
| 15:46 | <hax (HE Shi-Jun)> | It's look like PHP :P |
| 15:47 | <nicolo-ribaudo> | bakkot Right now attributes only support strings, but the idea is that they are easily extendable to support primitives that have literal syntax |
| 15:47 | <nicolo-ribaudo> | (the only reason they don't is due to lack of use cases so far) |
| 15:48 | <bakkot> | great, yeah |
| 15:50 | <hax (HE Shi-Jun)> | I heard dog barking...? |
| 15:51 | <nicolo-ribaudo> | It's unfortunately James's dog |
| 15:51 | <bakkot> | very loud dogs yes |
| 15:51 | <Michael Ficarra> | oh god |
| 15:51 | <nicolo-ribaudo> | oh dog |
| 15:56 | <James M Snell> | extremely bad timing, sorry about that |
| 15:57 | <bakkot> | https://www.jasnell.me/posts/fetch-needs-error-codes |
| 15:58 | <eemeli> | Historically, YAML libraries' approach to parse-immutable-ish concerns has been to define something like I don't think this is necessarily a good idea, but it's the closest historical precedent to the proposed change. |
| 16:10 | <eemeli> | I don't think anyone has supported frozen without a null proto, so it should be fine for the shorter name to be e.g. JSON.Frozen. |
| 16:13 | <Ashley Claymore> | yeah, and JSON.parse always creates packed arrays |
| 16:14 | <Michael Ficarra> | the reason it matters is that someone will want to call .slice on an array that was parsed from JSON data |
| 16:16 | <James M Snell> | https://webidl.spec.whatwg.org/#js-DOMException-specialness |
| 16:16 | <Lea Verou> | https://webidl.spec.whatwg.org/#js-DOMException-specialness
|
| 16:19 | <James M Snell> | Sorry again about the dogs... but at least they're cute |
| 16:19 | <eemeli> | You should take them on a walk. |
| 16:20 | <James M Snell> | That one is currently waiting on me to ^ |
| 16:24 | <hax (HE Shi-Jun)> | I don't like Decl in Cond if (let x = expr) have subtle different semantic with other language like Swift 😪 |
| 16:27 | <bakkot> | hax (HE Shi-Jun): what difference do you mean? |
| 16:27 | <bakkot> | fwiw there's already some subtle differences between languages |
| 16:27 | <bakkot> | with basically the same form |
| 16:32 | <hax (HE Shi-Jun)> | Swift semantic is like if (let x = expr; x != null) as Decl in Cond. |
| 16:36 | <rbuckton> | Rust can differentiate between a type and a variable in its pattern matching. JS can't. |
| 16:36 | <Nikolaos Papaspyrou> | A declaration cannot fail; pattern matching may either succeed (and declare) or fail. |
| 16:38 | <bakkot> | Olivier Flückiger:
|
| 16:44 | <nicolo-ribaudo> | For comparison, a way to write this if the variable was visible in
|
| 16:44 | <bakkot> | yeah |
| 16:44 | <bakkot> | or with a DisposableStack but that's even worse |
| 16:44 | <Olivier Flückiger> | yeah, or early return (depending) |
| 16:47 | <hax (HE Shi-Jun)> | They have overlap, for exmple, if (let x = ...) actually are pattern match in other languages like swift/rust/etc. |
| 16:53 | <ljharb> | keith_miller: to be fair, it had one presentation and then the champion left tc39, and it's only been reactivated for ~3 years, and 2 years ago it was seeking stage 2 before we got last-minute objections |
| 16:53 | <bakkot> | fwiw I strongly want to use this syntax for extractors as well:
I think that's a natural extension Ron really doesn't like this because he thinks it is not general enough for pattern matching, which I think inclines me against pattern matching rather than against this proposal |
| 16:55 | <nicolo-ribaudo> | Thoughts about cancelling the fourth day? |
| 16:55 | <Ashley Claymore> | Of every week? |
| 16:55 | <nicolo-ribaudo> | Yeah 3 consecutive days of work is already enough |
| 16:56 | <Clément Pit-Claudel> | Does Temporal support this? |
| 17:01 | <Lea Verou> | Just so I understand, we're debating whether this will be TDZ or a reference error? Because I think it is perfectly meaningful from the author side and I can see use cases for it. Could it be a mistake? Sure. But so could many other things. That's why we have linters. ``js
|
| 17:01 | <nicolo-ribaudo> | I think we had a ReferenceError for foo() = 1 and then converted it to SyntaxError |
| 17:02 | <Michael Ficarra> | why isn't the queue being advanced at all? |
| 17:03 | <Michael Ficarra> | oooh, that may be right |
| 17:03 | <bakkot> | https://github.com/tc39/ecma262/pull/1527 |
| 17:04 | <Michael Ficarra> | ooh thanks for the link @bakkot |
| 17:04 | <Michael Ficarra> | I have no memory of that PR at all |
| 17:05 | <bakkot> | fwiw I kind of agree but I also kind of think that the code you wrote is baffling too. if people don't try to write that very confusing code then they never run into this proposed error and don't have to learn about it |
| 17:05 | <bakkot> | but I would also, personally, leave this up to linters |
| 17:06 | <Justin Ridgewell> | Devin Rousso: Did you want a continuation to ask for Stage 2? |
| 17:06 | <bakkot> | Ron had an objection to stage 2 |
| 17:06 | <bakkot> | so I don't think there's any point |
| 17:06 | <Michael Ficarra> | at some point, I would like to rant about this committee's relationship with linters |
| 17:06 | <Jack Works> |
pattern matching gives you precised control over if a property should be matched or should be a declaration, so it may look like |
| 17:07 | <Devin Rousso> | i get the sense i won't get stage 2 due to the pattern matching objection so as much as i'd like to probably not ☹️ |
| 17:07 | <Ashley Claymore> | If something is ambiguous in the language I'd rather it wasn't allowed than I hope I never see it and have to think about it because I'll always get to work on teams that have a lint rule that bans it |
| 17:07 | <bakkot> | right, I get why you want that, but that syntax is so much worse |
| 17:08 | <Lea Verou> | I agree, but it's no more or less confusing than
which works just fine today. |
| 17:08 | <Michael Ficarra> | let them cook |
| 17:08 | <bakkot> | Lea Verou I generally think we should move away from "this existing thing is already confusing so it's ok to have a new confusing thing" as a justification |
| 17:09 | <bakkot> | see also "stop coercing things" |
| 17:09 | <Lea Verou> | Yes and no. Breaking internal consistency needs a stronger justification, and IMO it's less clear that this exists here. (re: https://www.w3.org/TR/design-principles/#consistency ) |
| 17:09 | <bakkot> | fwiw I don't agree: I think that is very clear, in a way your earlier snippet is not |
| 17:09 | <Justin Ridgewell> | Agreed. |
| 17:10 | <Devin Rousso> | and see my argument is that that syntax is SO different that there's no overlap (i.e. even if the semantics of declarations in conditionals imply one thing then the different syntax of that can imply another) |
| 17:10 | <Richard Gibson> | but it is more confusing, because the if binding isn't obviously out of scope inside else |
| 17:10 | <Justin Ridgewell> | I think if we want the binding to only be visible inside the If body, then the If is creating its own lexical scope in its body. |
| 17:10 | <Justin Ridgewell> | Either it's outside the If body and visible in the Else, or it's inside and not. |
| 17:12 | <bakkot> | it's inside and not visible, but that doesn't mean it's not confusing to see code which is written as if it is visible |
| 17:12 | <Justin Ridgewell> | Having it poison the else's reference to parent scopes seems wrong. |
| 17:12 | <Lea Verou> | Unless I'm misunderstanding your point, it seems that would argue for making the binding available in else, which I think is also defensible. |
| 17:13 | <Ashley Claymore> | The very fact that this committee think both being accessible and not accessible are both acceptable semantics proves that it is ambiguious. A way to avoid someone making the wrong assumption is to make the ambiguous case an early error |
| 17:14 | <Richard Gibson> | I think there are reasonable intuitions in both directions regarding visibility in else, which are clearly resolved by making references there invalid |
| 17:14 | <Justin Ridgewell> | It's only ambiguous because we want a special disposal time for using. The least confusing answer is it's visible with else. |
| 17:15 | <Olivier Flückiger> | it could also be an argument against the proposal if we cannot agree |
| 17:15 | <Lea Verou> | That makes sense, as a temporary thing: make them an error now, which buys us time to figure out what the best behavior is without compat issues getting in the way. We do that a lot in CSS, wasn't sure if it's a thing here. |
| 17:15 | <Ashley Claymore> | that is also true |
| 17:15 | <Olivier Flückiger> | I think this kinda goes back to an earlier mistake that was made, hoisting let up to the last block |
| 17:16 | <Ashley Claymore> | No, it is ambiguous. Because we have in these calls looked at the exact same code and assumed the value will be different things |
| 17:16 | <bakkot> | this is just not my intuition at all fwiw, even without using I think having it visible in the else would be more confusing/surprising (to me) |
| 17:16 | <Richard Gibson> | using motivates scoping to just the if, but conflicting intuitions would be there even without such motivation |
| 17:17 | <bakkot> | and yes I know it's visible in C++ (and Go, I'd forgotten) but those are... not languages I associate with non-confusing design decisions |
| 17:17 | <bakkot> | I actually hadn't known it was visible in C++ before looking it up, despite having used it > 100 times |
| 17:17 | <Lea Verou> | I wonder if we could reach a decision that nobody actively objects to, even if we don't agree with it |
| 17:17 | <Olivier Flückiger> | sorry but if let would start a scope in JS, then it would be less confusting. all the cases where it's not available in else are basically languages where let starts a scope and/or the if is a pattern match |
| 17:18 | <Olivier Flückiger> | in JS a scope is started by { exclusively. |
| 17:18 | <Olivier Flückiger> | in some cases like loops, we have sort of an imaginary { around the statement |
| 17:18 | <bakkot> | I just do not see this super clear distinction between "a pattern match" and "an if" |
| 17:18 | <Olivier Flückiger> | the only reasonable extension for the if/else statement is to have an invisible { around it |
| 17:18 | <bakkot> | it's still just an if |
| 17:19 | <Ashley Claymore> | for (let i = 0; ... |
| 17:19 | <bakkot> | loops are considerably more magic than that |
| 17:19 | <Olivier Flückiger> | a failing pattern match creates nothing. a declaration which assigns something which is not true, still creates a binding. |
| 17:19 | <bakkot> | but that's OK |
| 17:20 | <Olivier Flückiger> | I think the fact that we agree that we need to prevent people from using it wrongly hints at the fact that its maybe not the version we want. the design space is bigger... |
| 17:21 | <Lea Verou> | Yes, if the binding is available in else, the mental model is "there is an invisible scope here that encompasses the if() {} and the else {}". It's a little weird, but defensible. But I cannot see any reasonable mental model where it's TDZ. It breaks the user's mental model of what TDZ means. |
| 17:21 | <bakkot> | ok, so an if (let x = foo) is just a special kind of pattern matching where it's considered to fail if it's falsy, whatever? |
| 17:22 | <Richard Gibson> | I don't think anyone is still arguing for TDZ—early error has been deemed preferable |
| 17:22 | <Lea Verou> | Ironically, that makes the error option more defensible. :/ |
| 17:22 | <Jack Works> | and see my argument is that that syntax is SO different that there's no overlap (i.e. even if the semantics of declarations in conditionals imply one thing then the different syntax of that can imply another) is expression to switch to PM semantics, and forced to align with declaration in if condition, which requires the pattern be written in if (let _here_ = expr).Since it prefixed with let, it must completely compatible with destruction semantics, and push PM back to 2022 semantics (create binding by default).2022 version was presented and proved to a non-starter |
| 17:22 | <bakkot> | the purpose of an error is to avoid users having to form a mental model of it, because they will never read code where the question could arise in the first place |
| 17:23 | <bakkot> | in the same way you simply do not need a mental model of precedence for -2**2 |
| 17:25 | <Olivier Flückiger> | it's not a pattern match. that's why we can't have if (let [a,b] = foo()) { in this proposal. Because we don't ahve a notion of this not matching. |
| 17:25 | <Richard Gibson> | interestingly, it's somewhat analogous to how use strict in a function body is not compatible with non-simple parameters (which precede the body) |
| 17:26 | <Lea Verou> | Has anyone asked actual developers what their mental model is and how they'd expect this to work? That would be useful data to get. None of us is completely representative of the average developer, so we shouldn't be designing this based on our own mental model. E.g. it may turn out that 99% of them expect it to be available. Or to just access the one from the parent scope. Or they may genuinely expect different things, in which case, error it is. |
| 17:27 | <bakkot> | I don't know what "it's not a pattern match" means. You said "a pattern match" is a thing where a failing match doesn't create a binding. ok, great, if (let x = y) doesn't create a binding if y is falsy. There are additional syntactic forms that you can't write but I don't see why that changes how if (let x = y) is interpreted |
| 17:28 | <Richard Gibson> | haven't we already definitively established "they may genuinely expect different things" without even looking outside this group? |
| 17:28 | <bakkot> | incidentally I would be totally fine with making if (let [a, b] = foo()) legal, and making it take the else in the case that foo() is null (rather than throwing as it currently does). |
| 17:28 | <bakkot> | (but not in the MVP) |
| 17:29 | <Lea Verou> | It doesn't follow that because folks in this group have diverging mental models, regular developers at large will also have diverging mental models. It's likely, but not so likely that getting actual user data is pointless. |
| 17:31 | <peetk> | does anyone a) strongly prefer binding in else and also b) really like and want this proposal? if so why? |
| 17:32 | <Richard Gibson> | I don't think collecting user data on this question is pointless, but I do think it is unnecessary. This group is a subset of the larger one, so we already know that the latter contains differing intuitions, even if they are (currently) wholly confined to our subset. |
| 17:33 | <Devin Rousso> | that's a lot of ifs. and i don't get why declarations in conditionals would require pattern matching to drop is. why can't both exist? |
| 17:35 | <Justin Ridgewell> | I don't think any user is really expecting this tranpsilation:
|
| 17:35 | <bakkot> | I am expecting that transpilation |
| 17:36 | <Jack Works> | that's a lot of ifs. and i don't get why declarations in conditionals would require pattern matching to drop if) |
| 17:36 | <Justin Ridgewell> | That is the semantics you expect, through? |
| 17:36 | <bakkot> | or rather, I am using this feature, and am expecting the using to be disposed before the else, and am not thinking about the transpilation because that's a weird thing to think about |
| 17:36 | <Justin Ridgewell> | Unless you want to force transpiling using, too. |
| 17:36 | <Devin Rousso> | but those arent the same thing! you said it yourself: test vs create a new binding |
| 17:36 | <Lea Verou> | That …is not how user research works. TC39 is not a representative subset of developers who write JS, just like the CSS WG is not a representative subset of developers who write CSS. There are huge selection biases that make a certain type of developer get into standards. :) |
| 17:37 | <Justin Ridgewell> | But this is how I form mental models of new syntax. "How would I write this in the old syntax" |
| 17:37 | <Justin Ridgewell> | I think that's common. |
| 17:37 | <bakkot> | uhhh I think most people are not babel maintainers |
| 17:37 | <Justin Ridgewell> | That old syntax transpilation is not obvious. |
| 17:37 | <bakkot> | also it's only "new" syntax for a few years, after which it's just syntax |
| 17:38 | <bakkot> | the actual transpilation of for (let x is also not obvious |
| 17:38 | <bakkot> | but no one cares because it just works the way they expect |
| 17:38 | <bakkot> | if you actually write it out, it's pretty weird! |
| 17:38 | <Richard Gibson> | I'm not claiming that TC39 are representative. But that property is not necessary for an existence proof. |
| 17:38 | <Justin Ridgewell> | That's because of block scoping, and I think everyone wanted the let behavior from the beginning. |
| 17:39 | <bakkot> | it is not just because of block scoping |
| 17:39 | <bakkot> | even with block scoping it has additional magic |
| 17:40 | <Justin Ridgewell> | That the binding is available within everything inside the for's syntax? That makes it seem like if { } else { } should get it too. |
| 17:40 | <Justin Ridgewell> | The downlevel to for (var …) is obvious, it's only the block scoping that makes it magic. |
| 17:41 | <bakkot> | sorry, to spell it out, the additional magic is that the binding is per-iteration but ++i still works, somehow |
| 17:42 | <Justin Ridgewell> | That is, explicitly, block scoping's magic. |
| 17:42 | <Justin Ridgewell> | for (var i; i; i++) downlevel is easy to explain. |
| 17:42 | <bakkot> | I don't think of "block scoping" as meaning "we do weird things with loop updates", but sure, if that's what you mean then it is block scoping's magic |
| 17:42 | <Justin Ridgewell> | It's only block scoping's var capture for closures that has anything magical. |
| 17:43 | <bakkot> | no, it's the loop update which is magical here? |
| 17:43 | <bakkot> | anyway whatever |
| 17:43 | <bakkot> | the point is, this works because of, for (let x is a special syntactic form with a complex downleveling, and that's fine |
| 17:43 | <bakkot> | because no one actually thinks about the complex downleveling |
| 17:43 | <Justin Ridgewell> | Please explain why for (var i; i; i++) is wrong? |
| 17:43 | <bakkot> | it is fine, good even, to add new syntactic forms with good behavior but complex downlevelings. if they didn't have a complex downleveling they'd be less worth adding! |
| 17:43 | <Lea Verou> | It's not about existence though. Every language concept has been misunderstood by someone, somewhere. It's about distribution of expectations. If 99% of developers expect A and 1% expect B, it would be a poor design choice to throw because "we can't possibly know". But if 70% expect A and 30% expect B it's defensible. |
| 17:44 | <Jack Works> | but those arent the same thing! you said it yourself: test vs create a new binding I mean test and create new binding if (let decl = expr()) for this simple case I think it maybe ok because decl in if is better in this case if (let { x, y } = point(); x === 0) then the committee may think it's a big overlap and require pm to match inside the let { ... } |
| 17:44 | <bakkot> | I have not said anything about for (var i being wrong and am confused why you are asking about it? |
| 17:44 | <Justin Ridgewell> | I think your Promise.try proposal points to this being wrong. We form a mental model of the code based on the way we'd write it now. |
| 17:45 | <Justin Ridgewell> | Because, literally my whole point, is that you form expectations based on how you'd write it today. |
| 17:45 | <bakkot> | we form a mental model of the code based on what we think it is. What I think Promise.try is is, "it's for calling wrapping sync errors", which suggests it should not do any additional stuff in the non-sync-error case |
| 17:46 | <Justin Ridgewell> | for (let …)'s transpilation is just var, unless you have a very specific case with block scoping and a closure. |
| 17:46 | <Justin Ridgewell> | It's extremely easy to explain. |
| 17:46 | <bakkot> | yes, block scoping and closures is the whole thing I am referring to wrt magic here |
| 17:47 | <Richard Gibson> | I understand this reasoning, and agree with it in many cases, but not in this one. Just like making -2**2 invalid syntax would still be reasonable even if 99% of then-current developers expected it to be negative. |
| 17:47 | <Justin Ridgewell> | The obvious transpilation of if (let x…) is let x; if (…) |
| 17:47 | <bakkot> | that is the obvious to you transpilation |
| 17:47 | <bakkot> | but to me if I see if (using x = ...) { } else { } I am really expecting the using to be disposed before the else |
| 17:47 | <Justin Ridgewell> | I think that's going to be the way 99% of people think of it. |
| 17:48 | <bakkot> | it would be (to me) completely insane for it not to be disposed before the else |
| 17:48 | <bakkot> | again, I strongly disagree with the claim that 99% of people think of transpiling code when reading code |
| 17:48 | <Justin Ridgewell> | I do not think anyone is going to expect this tranpsilation, because I struggled to come up with it. |
| 17:49 | <bakkot> | I don't think almost anyone would come up with the correct transpilation of for (let x = ...) setTimeout(() => x) either |
| 17:49 | <bakkot> | but that's ok! |
| 17:49 | <bakkot> | because people don't think in terms of transpilation! |
| 17:51 | <Justin Ridgewell> |
We teach closure capturing as an interview gotcha question |
| 17:51 | <Devin Rousso> | does pattern matching create bindings in that destructuring example? or is it just testing whether it could destructure as described? |
| 17:53 | <bakkot> | right. it's a gotcha precisely because the transpilation is unexpected. also, that suggests that you wouldn't be able to do an early return, so if you do think in terms of transpilation and that's your mental model you will still be confused. but in practice it is fine because, again, people do not think in terms of transpilation |
| 17:55 | <Justin Ridgewell> | How would you break inside any closure?
I think everyone does. |
| 17:55 | <Justin Ridgewell> | It's just how new syntax features are taught. |
| 17:55 | <Lea Verou> |
This is exactly why we're not representative users 😂 |
| 17:58 | <Aki> | Your employer's interviewing style doesn't exactly have the best reputation for like… actually filtering for people who will be good at their actual jobs. |
| 17:58 | <Justin Ridgewell> | It's been banned for a while |
| 17:58 | <bakkot> | no one teaches this https://babeljs.io/repl#?config_lz=N4IgZglgNgpgdgQwLYxALhAJxgBygOgCsBnEAGhB22JgBdS0BtRkeAN3NFoUwHM6GIACYwwCAK5R6ZAARwA9rRkQYMgIxrZCpSoD6SeQCN1a8iANDJMBmKg0KYeZgDGMAIJQoAFUwI4xR0wkBlpMcRgAXwBdMhZibn4AWgBmTmEYZycEWidiADUYTGIIeTh0EAAmAAYK5MSNMxwIHBgoCDgYAAVMeRx5YgQocqR2iCRBkGiYkGJ5cRcYLwBPFvLiZ0xm2kmgA&code_lz=GYewTgBAFANgpgFwgDwgXggBgNwogHggGZcBqU5ASggG8AoCRiAZ0QBUBLAWzhAFcEUKNTQA-FOUrYGTDsGgBjEADsAJhwQcVw6gCMwcAIYBraUxbtuvAUJHjkk6QF8gA&lineWrap=true&version=8.0.4 |
| 17:58 | <bakkot> | that is just not true |
| 17:59 | <bakkot> | they give an approximation which is good enough to get a sense of what it does |
| 18:00 | <Justin Ridgewell> |
|
| 18:01 | <bakkot> | that has different semantics |
| 18:01 | <Aki> | 👋🏻 hi could someone please volunteer to edit notes for an hour |
| 18:01 | <bakkot> | |
| 18:02 | <Aki> | ❌❌❌❌❌❌❌❌❌ |
| 18:02 | <Aki> | hehe whoa that is not how i thought that would look |
| 18:05 | <Justin Ridgewell> | Or if we really want:
|
| 18:06 | <Lea Verou> | Re: developer expectations, for anyone interested I just posted: Still too early to draw conclusions (and polling on social media has its own, separate, biases obvs), but could be interesting as another data point. |
| 18:06 | <Michael Ficarra> | I am so excited to get async iterator helpers rolling |
| 18:06 | <Justin Ridgewell> | And then it's just block scoping's transform. |
| 18:06 | <Jack Works> | does pattern matching create bindings in that destructuring example? or is it just testing whether it could destructure as described? current pattern matching does not create bindings by default, some examples:
|
| 18:07 | <Justin Ridgewell> | Need to run to a meeting |
| 18:08 | <Michael Ficarra> | buffered should totally be in the MVP |
| 18:08 | <Olivier Flückiger> | at least the bluesky post looks a bit biased as basically it focuses on the case where the else value is unexpected... |
| 18:09 | <Lea Verou> | I was trying for a snippet that is (a) short and (b) tests the else case, which is where the bone of contention lies. What would you use? |
| 18:11 | <Olivier Flückiger> |
|
| 18:11 | <peetk> | what's the goal? i think throw advocates acknowledge that throw is pretty novel semantics and thus wouldn't be anticipated by anyone. the point of throwing is people don't have to have a clear intuition. and throw mostly protects against a case where the developer doesn't realize the bound variable is shadowed |
| 18:12 | <Lea Verou> | Fair, that would be a little clearer. But do you think the difference might be significant enough to bias the results? |
| 18:13 | <Lea Verou> | Yes, it's not about "do people expect an error", it's about "what's the split between A and B". D only exists to keep the A vs B cleaner (instead of those who actually expect an error being forced to pick one of the two) |
| 18:18 | <Devin Rousso> | ok so yeah again there's a lot of syntax and behavior differences between those two. and for the few (debatably simple) scenarios that overlap it's a side-effect of the expressiveness of pattern matching, not an explicit goal |
| 18:19 | <Michael Ficarra> | @Lea Verou you should ask them what they think this should do: |
| 18:20 | <Michael Ficarra> | I don't expect anybody will want it to log globalThis.name |
| 18:20 | <Devin Rousso> | @jackworks:matrix.org but more importantly, i don't really see how this conflict concern can be resolved without syntax/behavior/etc. changes to declarations in conditionals which likely would result in bringing in parts of pattern matching which is probably not something we would want |
| 18:22 | <Steve Hicks> | I find this case very compelling. My mental model is that if (x) { log(y); } else { log(y); } should be reducible to log(y);. |
| 18:23 | <Michael Ficarra> | which is exactly why we should ban it |
| 18:23 | <Michael Ficarra> | there's no good option where it runs and does something |
| 18:23 | <Steve Hicks> | Even if "x" is "let y = foo()" then I'd still expect log(foo()) to be equivalent. |
| 18:25 | <nicolo-ribaudo> | This demo is very cool |
| 18:26 | <Olivier Flückiger> | I don't think anybody would expect it to throw either.... |
| 18:27 | <Michael Ficarra> | nobody is asking for it to throw |
| 18:27 | <Michael Ficarra> | we're asking for it to be an invalid program |
| 18:27 | <Michael Ficarra> | just like we did with exponentiation because nobody could agree what it should do |
| 18:28 | <Olivier Flückiger> | yeah, we only have runtime errors here. some are a bit earlier some later |
| 18:28 | <Olivier Flückiger> | this is actually gonna break lazy parsing btw. |
| 18:28 | <Ashley Claymore> | Developers will see SyntaxErrors very early... |
| 18:29 | <Ashley Claymore> | in their editor |
| 18:29 | <Olivier Flückiger> | one of the most important optimizations for the web where 80% of code is dead... |
| 18:29 | <Michael Ficarra> | that's not exactly an accurate model since it prevents earlier code from running |
| 18:29 | <Ashley Claymore> | maybe that was the motivation for TDZ over SyntaxError |
| 18:30 | <Olivier Flückiger> | might break, have to think about it some more |
| 18:30 | <nicolo-ribaudo> | Isn't it similar to how let a = 1; ... something ... let a = 2 is a syntax error? |
| 18:30 | <nicolo-ribaudo> | The parser needs to do some scope analysis |
| 18:31 | <Olivier Flückiger> | yes it does. I am not sure if it also looks at all uses |
| 18:31 | <Olivier Flückiger> | as i said, I have to think about it |
| 18:31 | <Lea Verou> | Yup, I think I agree with Michael Ficarra . It doesn't seem that developer expectations are sufficiently concentrated around either option, and an error can always become a not-error later, whereas if we pick either behavior, we're stuck with it forever. |
| 18:39 | <eemeli> | Urrr, would if (var x = y()) ... work? And if it does, would it too error for x access in the else? |
| 18:40 | <ljharb> | i assume it'd only allow let/const and not var |
| 18:40 | <nicolo-ribaudo> | I would be very in favour of just bannig var there |
| 18:40 | <Michael Ficarra> | it only allows let/const/using |
| 18:40 | <nicolo-ribaudo> | Regardless of which visibility we choose |
| 18:42 | <Steve Hicks> | var could let us have our cake and eat it too, since I'd absolutely expect the var binding to persist to the else... not that it's a good idea. |
| 18:43 | <Ashley Claymore> | Well var wouldn't be scoped at all, so there's no (real) benefit to putting it in there |
| 18:43 | <eemeli> | We do allow for (var x = .... |
| 18:44 | <ljharb> | that predates let/const tho |
| 18:44 | <eemeli> | Benefit: not introducing a need to think of where which declarations are allowed or not. |
| 18:44 | <Michael Ficarra> | for-var-of doesn't 😜 |
| 18:45 | <ljharb> | yeah true |
| 18:45 | <nicolo-ribaudo> | What if we allowed only var |
| 18:45 | <ljharb> | you already get that benefit by never using var ever (when targeting engines that support let/const, ofc) |
| 18:46 | <Lea Verou> | I think it makes sense to allow it. Per eemeli 's argument, it's allowed everywhere else (edit: almost) and this would introduce another inconsistency that developers have to memorize (though as already mentioned, there's precedent). OTOH, it's mostly moot as developers have been so warned not to use var that they won't use it even where it would actually make their code cleaner because it's easier to just use let/const than to have to evaluate on a case by case basis. |
| 18:47 | <eemeli> | Seriously, I think we should allow var, even if it does exactly the same thing as if you had the declaration before the if. |
| 18:47 | <ljharb> | imo there's no excuse for hand-writing var once one is targeting let/const-supporting engines (there's one case for transpiler output of it, though, but that doesn't affect this) |
| 18:51 | <Lea Verou> | A while back I briefly experimented with adopting it for try ... catch where the scoping is typically undesirable, but that didn't last long, everything and everyone treats it as a mistake rather than a conscious choice. |
| 18:51 | <nicolo-ribaudo> | I like using
|
| 18:51 | <nicolo-ribaudo> | Oh well that :) |
| 18:52 | <ljharb> | we need to ship do expressions to solve that one :-p |
| 18:52 | <nicolo-ribaudo> | Real world example: https://github.com/babel/babel/blob/61bfda575b03e6a32af7877b65651a37eb339426/packages/babel-core/src/config/full.ts#L116 |
| 18:55 | <Michael Ficarra> | try scoping is the next thing we should tackle after we get through this if/else scoping proposal |
| 18:55 | <Michael Ficarra> | those are the two places that I am constantly bothered by them being too broad |
| 18:55 | <Ashley Claymore> | ES2028 - the one where we fixed try |
| 18:55 | <eemeli> | Tackle how? |
| 18:56 | <Michael Ficarra> | I imagine it will just be try expressions |
| 18:56 | <Michael Ficarra> | give the developer a way to write a try that is scoped to exactly the thing they think might throw |
| 18:57 | <Michael Ficarra> | I hate seeing big blocks with like 10 statements inside a try/catch where the developer really only expects 1 little tiny expression in 1 of those statements to possibly throw |
| 18:57 | <Michael Ficarra> | but because of scoping, that's the easiest thing for them to do |
| 18:57 | <Michael Ficarra> | it's the same issue that we're solving with @Devin Rousso's proposal IMO |
| 18:57 | <ljharb> | to ship try expressions requires the same "completion value" question that blocks do expressions, and if we can resolve it for one we can do it for the other, so let's just advance do expressions :-) |
| 18:58 | <peetk> | python style try-else?? |
| 18:58 | <Michael Ficarra> | I think one of them was slightly harder syntactically |
| 18:58 | <eemeli> | Pattern matching on catch would be really nice. |
| 18:59 | <Lea Verou> | Another idea might be a token to turn scoping off for certain blocks (a bit like regex's (?:) non-capturing groups). I just thought of this, so I may think it's a terrible idea later, but figured I'd put it out there :P |
| 18:59 | <nicolo-ribaudo> | A try expression could take an expression rather than a statement |
| 18:59 | <nicolo-ribaudo> | Like the current try expression proposal from somebody from the community does |
| 19:00 | <nicolo-ribaudo> | The one that returns a "result" |
| 19:00 | <Ashley Claymore> | Need extractors first... |
| 19:01 | <ljharb> | Pattern matching on |
| 19:01 | <Michael Ficarra> | we're going to need a continuation of this async iterator helpers topic |
| 19:02 | <Michael Ficarra> | we really need to give Kevin feedback so he can make progress |
| 19:03 | <Olivier Flückiger> | I for my part am very happy with @erights Mark Miller (Agoric) MM setting the pace. If we run out of timebox then it's not because we are slow, but because it is complicated. |
| 19:03 | <peetk> | pattern matching but ONLY in catch |
| 19:04 | <Michael Ficarra> | I'm not complaining about us being slow, just saying it would be a shame to not finish giving Kevin feedback during this plenary (but we have plenty of time left at the end to continue, so I'm not worried) |
| 19:05 | <Michael Ficarra> | do failed pattern matches suppress or do they get rethrown? |
| 19:06 | <Justin Ridgewell> | Sorry, had another meeting. Why doesn't filter have a fast path for out-of-order? |
| 19:06 | <Justin Ridgewell> | The way we're discussing for map? |
| 19:07 | <eemeli> | Rethrow, probably? |
| 19:07 | <Michael Ficarra> | because you don't know which promise you can resolve with it while retaining the correct output ordering |
| 19:08 | <Justin Ridgewell> | But we're discussing that very thing for map? |
| 19:08 | <Michael Ficarra> | because for map you do know |
| 19:08 | <Justin Ridgewell> | Why is ordering important for filter but not map? |
| 19:08 | <Michael Ficarra> | input-output are 1:1 for map, not so with filter |
| 19:08 | <Olivier Flückiger> | agreed, but going to the next point can also evoke the expectation that the previous one is resolved. |
| 19:08 | <Michael Ficarra> | did someone ask to move along to the next point? |
| 19:09 | <Justin Ridgewell> | I don't understand. |
| 19:09 | <nicolo-ribaudo> | Do y'all see Ashley's slides as fullscreon or in a big black rectangle? |
| 19:09 | <Chris de Almeida> | not full |
| 19:09 | <Justin Ridgewell> | Tiny little rectangle |
| 19:09 | <nicolo-ribaudo> | It is weird but I guess fine, not worth trying to fix it |
| 19:10 | <Michael Ficarra> | I know that the third input value corresponds to the third output value for map; for filter, it could correspond to any of the first, second, third, or none of them (be dropped) |
| 19:11 | <Justin Ridgewell> | I get the words you're saying, I don't get the impl. How would map know this and not filter? |
| 19:11 | <Justin Ridgewell> | Why would it matter? If map can go out of order, why couldn't filter? |
| 19:11 | <Justin Ridgewell> | If filter can't, why would map? |
| 19:11 | <Michael Ficarra> | map resolves out of temporal order, but the values are yielded in sequence |
| 19:12 | <Michael Ficarra> | this entire proposal is order-preserving |
| 19:12 | <Michael Ficarra> | the out-of-order helpers are in a separate proposal (https://github.com/tc39/proposal-unordered-async-iterator-helpers) |
| 19:13 | <bakkot> | Justin Ridgewell: it is my hope that you can stare at https://bakkot.github.io/async-iterator-helpers-implementation/#map-concurrent and https://bakkot.github.io/async-iterator-helpers-implementation/#filter-concurrent-2 and be enlightened |
| 19:13 | <bakkot> | just play those scenarios out (hit right arrow or next repeatedly) |
| 19:14 | <hax (HE Shi-Jun)> | If want this, I think we should reserve if (let x = val) for consistency. (current proposal make it same as if (let x = val; val). |
| 19:14 | <Justin Ridgewell> | OHHHHH |
| 19:14 | <bakkot> | but, to put it in words: in either scenario, you do two pulls from the result. this does two pulls from the underlying. suppose the second settles and then the mapper settles / the predicate returns true for that value. for .map, you know that this value is going to go into the second result promise for .filter, it might not! |
| 19:15 | <Justin Ridgewell> | That was very helpful |
| 19:15 | <bakkot> | yaaaaay |
| 19:16 | <Michael Ficarra> | that totally justifies the hours and hours Kevin spent on the demo |
| 19:18 | <Michael Ficarra> | a 100-value composite seems a little bit unrepresentative of real code |
| 19:18 | <Michael Ficarra> | I would think they'd almost always be 2 or 3 values with a sharp drop off |
| 19:20 | <Michael Ficarra> | oh, I think both graphs are labelled the same but the first graph actually is only 3 values |
| 19:20 | <Justin Ridgewell> | Ok, the demo looks like different scenarios for the function, and not different semantics |
| 19:20 | <bakkot> | yes, demo just has one semantics implemented |
| 19:20 | <bakkot> | well, one for each of the three |
| 19:20 | <bakkot> | first link is a scenario for .map, second link is a scenario for .filter |
| 19:20 | <Justin Ridgewell> | Yah |
| 19:21 | <bakkot> | incidentally something like 80% of the complexity of this proposal is flatMap alone and we barely got to it, so, we have at least another hour of material to talk about I expect |
| 19:24 | <Michael Ficarra> | wait until you hear my opinions about flatMap, it's gonna be even more |
| 19:24 | <bakkot> | I had fable implement your thing, +647 -224 |
| 19:24 | <bakkot> | I can do better but it's still going to be that order of magnitude |
| 19:25 | <bakkot> | I guess only +242 -131 for the actual implementation though, that was mostly tests |
| 19:32 | <Michael Ficarra> | I actually like the name Composite |
| 19:37 | <rbuckton> | I wish I were in a better position to discuss pattern matching, but all I have with me is my phone at the moment (and this takes awhile to type out). I looked into various ways to adopt pattern about 4 years ago now after researching the approaches taken in Rust, Swift, Python, C#, and a few other languahes, and was initially optimistic about the possibility of I found, though, that the C# syntax and semantics were a good fit. C# separates destructuring (called "Deconstruction") from pattern matching and has both a branching
or
or just
to both match and extract deeply nested values without bind/reference syntax confusion, while avoiding extra syntax for the most common cases (testing against types). I find patterns like |
| 19:37 | <Justin Ridgewell> | Is there a scenario where ordering matters? If we held done results, and pushed pending calls first, could that simplify everything? |
| 19:37 | <Justin Ridgewell> | Then the consumer still sees a coherent stream. |
| 19:38 | <bakkot> | is there a scenario where the order of values from an iterator matters? to ask the question is to answer, surely? |
| 19:39 | <Michael Ficarra> | @bakkot I think this is just more support for my hypothesis that everyone is basically always going to want to use the unordered variants of these things |
| 19:39 | <rbuckton> | (a few typos, sorry. Not going to try fixing them because edits in Element on mobile always mess up formatting) |
| 19:39 | <Justin Ridgewell> | How would a consumer associate the pulled promise with the internal map/filter's call? |
| 19:39 | <Justin Ridgewell> | I don't know if there's a way for them to hold an identity where they could prove it |
| 19:40 | <bakkot> | possibly I am not understanding your suggestion. my position is: if the we do [1, 2, 3].toAsync().map(f).buffered(3).toArray(), we should get [f(1), f(2), f(3)] out, no matter what order the calls to f settle in |
| 19:40 | <bakkot> | if your suggestion does not violate that property then I do not understand your suggestion |
| 19:41 | <Justin Ridgewell> | Sorry, I'm not sure what buffered does |
| 19:41 | <Justin Ridgewell> | How does it interact with the fast delivery of map? |
| 19:42 | <bakkot> | pulls N times immediately, stores the resulting Promises in an internal buffer, hands them out in order to callers |
| 19:42 | <bakkot> | it doesn't inspect results of Promises at all |
| 19:42 | <Justin Ridgewell> | If f(2) returns done, does it matter that you receive [1, done, 3] or [1, 3, done]? |
| 19:43 | <Justin Ridgewell> | How does the consumer of the buffer associate the internal calls? |
| 19:43 | <bakkot> | I really really do not want to get [1, 3, done] in that case, map should not be changing order of results |
| 19:45 | <Justin Ridgewell> | I guess that does force an internal buffer of all things like filter. |
| 19:45 | <bakkot> | I don't understand the question.
|
| 19:45 | <bakkot> | modulo some handling of .return etc |
| 19:46 | <Justin Ridgewell> | My point was that whoever consumed the buffer couldn't tell which order the internal calls were made |
| 19:46 | <bakkot> | oh, yes, I agree with that, and it's OK / good to do the internal calls to the mapper/predicate function out of order |
| 19:46 | <Justin Ridgewell> | But this just becomes like a filter |
| 19:47 | <bakkot> | as long as the observed results are the same order you'd get if there was no concurrency |
| 19:47 | <Justin Ridgewell> | If we wanted to hold done values to reorder |
| 19:51 | <Michael Ficarra> | undefined values are dropped in JSON serialisation because JSON doesn't have undefined lol |
| 19:52 | <Michael Ficarra> | I don't think that says anything about how the language should behave |
| 19:53 | <bakkot> | I do occasionally kick around the idea of a { x?: v } syntax that doesn't create x if v is undefined |
| 19:54 | <bakkot> | if we had that syntax, I would feel more strongly that undefined should not be treated as absent, because if it's present it's there on purpose |
| 19:56 | <bakkot> | did everyone else just get kicked out |
| 19:56 | <nicolo-ribaudo> | Lol |
| 19:56 | <Lea Verou> | just came to ask the same question |
| 19:56 | <nicolo-ribaudo> | Yes |
| 19:56 | <Olivier Flückiger> | yes |
| 19:57 | <nicolo-ribaudo> | Are we on a free teams plan? |
| 19:57 | <Aki> | hmm. |
| 19:57 | <Ashley Claymore> | "this meeting is going on too long" |
| 19:57 | <Michael Ficarra> | well that was unfortunate timing |
| 19:57 | <peetk> | that's one way to timebox |
| 19:57 | <James M Snell> | That's.... fun |
| 19:57 | <Richard Gibson> | wow, judgmental much? |
| 19:57 | <Olivier Flückiger> | I think we all agree, thanks teams |
| 19:57 | <Michael Ficarra> | can we agree never to use Teams ever again? |
| 19:57 | <Aki> | Drag me, teams. |
| 19:57 | <Justin Ridgewell> | Lol |
| 19:58 | <James M Snell> | That's not a timebox, that's a time hammer |
| 19:59 | <Olivier Flückiger> | so I guess restart tomorrow? I hope somebody can restart it |
| 19:59 | <James M Snell> | For those who were dropped and have not yet rejoined... you can use the same link to just rejoin |
| 20:00 | <James M Snell> | but we're almost done anyway |
| 20:00 | <Chengzhong Wu> | btw refreshing the page won't work. Join with the link again |
| 20:04 | <Michael Ficarra> | yes this is what I was suggesting: we should solve that problem for object initialisers, it's not a problem for composites to solve |
| 20:04 | <bakkot> | Ashley Claymore: my topic was, IIUC you were concerned that people would write would you feel differently if we had syntax where they could write |
| 20:04 | <Aki> | Woof the google doc's summaries and conclusions are sadddddd so far. I apologise for not being more annoying earlier. |
| 20:04 | <Aki> | DON'T FORGET TO WRITE YOUR SUMMARIES AND CONCLUSIONS |
| 20:04 | <Aki> | I WILL FIND YOU |
| 20:05 | <James M Snell> | I did mine already so I wouldn't incur Aki's wrath |
| 20:05 | <Michael Ficarra> | there's still like 16 days left, I'm not worried |
| 20:05 | <Aki> | MF out here conspiring to give me a stroke |
| 20:05 | <Michael Ficarra> | I'll get to it in 15 days |
| 20:06 | <bakkot> | though, maybe we shouldn't do this because it encourages creating objects with different shapes, idk |
| 20:07 | <James M Snell> | Personally I'd prefer to avoid syntax and use something like new Composite({a: 0, b: possiblyUndefined}, { omitUndefined: true }) ... but I suspect I might be in the minority. I don't like special casing undefined by default |
| 20:08 | <James M Snell> | To be fair, 15 days, 23 hours and 50 minutes or so |
| 20:08 | <James M Snell> | give or take |
| 20:08 | <bakkot> | James M Snell: partly I want this for completely unrelated reasons: I want to be able to write x = { ...y, v?: undefined } to get all the properties of y except for v (and not have a stray v: undefined on x) |
| 20:08 | <Michael Ficarra> | if they want to make objects different shapes, they're gonna make objects different shapes |
| 20:09 | <Michael Ficarra> | whether it's more readable/maintainable or not |
| 20:09 | <bakkot> | I mean, no, mostly they create objects with extraneous undefined properties currently |
| 20:10 | <Michael Ficarra> | oh sure, you're saying some portion of them will give up and just fill with undefined instead if it's too wordy |
| 20:10 | <Michael Ficarra> | yeah I buy that |
| 20:11 | <James M Snell> | I just think it's weird that if I have Composite({ b: undefined, a: 0 }) that would be treated the same as Composite({ a: 0, b: undefined }) while Composite({ b: null, a: 0}) would be distinct from Composite({ a: 0, b: null}) |
| 20:11 | <Richard Gibson> | is that being proposed? I thought property order was irrelevant. |
| 20:12 | <James M Snell> | Proposal shows, for instance, Composite({x : 1, y: 2}) -> Object.keys(c); // ["x", "y"] ... I assume order ends up mattering |
| 20:12 | <James M Snell> | unless there's a normalization I missed |
| 20:13 | <Richard Gibson> | I believe there is. That's the key sorting Ashley Claymore was talking about |
| 20:13 | <James M Snell> | ah! ok, then I missed that part of the discussion entirely thanks to dogs ;-) |
| 20:14 | <James M Snell> | still, even with that, I'm not seeing a strong reason to special case undefined |
| 20:16 | <Richard Gibson> | I agree. And we didn't even discuss -0 this time, which has similar possibilities |
| 20:17 | <bakkot> | -0 :( |
| 20:18 | <Ashley Claymore> | If we intern we have to normalize -0 IMO |
| 20:19 | <Ashley Claymore> | normalize to 0 |
| 20:19 | <Richard Gibson> | interning vs. normalizing primitives into equivalences classes seem like completely independent decisions to me |
| 20:19 | <Ashley Claymore> | -0 === 0 |
| 20:20 | <Ashley Claymore> | So a composite should be === |
| 20:20 | <Richard Gibson> | and NaN !== NaN |
| 20:20 | <Richard Gibson> | === is not an identity comparator |
| 20:20 | <Ashley Claymore> | I'm talking about intuition |
| 20:21 | <Ashley Claymore> | we also can't keep talking about -0 and NaN. it's been over 5 years |
| 20:21 | <bakkot> | the point of interning is that it lets you do === cheaply i.e. without a recursive walk; if it doesn't let you do === cheaply there is no point |
| 20:22 | <Ashley Claymore> | Exactly. Interning has to mean actual interning here |
| 20:22 | <Ashley Claymore> | Not: maybe intern but not for -0 and NaN |
| 20:23 | <Richard Gibson> | that's true but immaterial... actual interning can be achieved with or without primitive normalization |
| 20:23 | <Richard Gibson> | they are independent |
| 20:24 | <Ashley Claymore> | true but not if we also want 0 and -0 to remain equal in a key, which I want |
| 20:24 | <Ashley Claymore> | We already normalize them for Map and Set |
| 20:24 | <Ashley Claymore> | That this what we should do here |
| 20:24 | <Ashley Claymore> | Anything else is very unintuitive for 99.9% of JS developers |
| 20:25 | <Ashley Claymore> | I frequently ask seniors engineers what they think about the equality of negative zero in floating point numbers and it's the first time they have ever heard of negative zero |
| 20:26 | <James M Snell> | A case like
Is likely to be a bit confusing for users... likely an edge case but still ... Is the proposal still planning to expose the members of composite as properties on the composite? |
| 20:27 | <Ashley Claymore> | I'm happy to change my stance on (not) including undefined values |
| 20:27 | <Ashley Claymore> | I get the feeling that my original stance is in the minority |
| 20:27 | <James M Snell> | I'm just trying to poke around edge cases to see if I can convince myself that my opposition to ignoring undefined is incorrect |
| 20:30 | <Ashley Claymore> | I was mostly coming from the perspective of how prevalent JSON.stringify is used for keys today, and I hadn't come across dropping undefined as an issue. whereas the key order and unsupported types were |
| 20:32 | <James M Snell> | yeah, and I can absolutely appreciate that view. I can see it both ways but special casing it just feels more incorrect. Going to have to stew on it for a bit |
| 20:33 | <Ashley Claymore> | and |
| 20:34 | <Ashley Claymore> | It would be catastrophic to defining interning a key that has NaN within it to always mint a new key |
| 20:34 | <Ashley Claymore> | Just so they are not equal |
| 20:34 | <Ashley Claymore> | so NaN in a key must be equal |
| 20:35 | <Richard Gibson> | yes, Set and Map both have opinionated non-configurable key normalization that doesn't correspond with === |
| 20:35 | <Ashley Claymore> | And throwing would be a real shame. If that's the only case we throw on |
| 20:35 | <bakkot> | honestly that specific example is fine for me, I don't care that much about equality for keys containing NaN |
| 20:35 | <Ashley Claymore> | yes, Set and Map both have opinionated non-configurable key normalization that doesn't correspond with the interning will define the equality in Map and Set |
| 20:36 | <bakkot> | I do think it's important that Composite({ x: -0 }) === Composite({ x: 0 }), which implies one of: not interning, a full walk for === despite interning, normalization, or throwing |
| 20:36 | <bakkot> | I think we should rule out the "full walk despite interning" option, and throwing feels bad |
| 20:39 | <Ashley Claymore> | https://acutmore.github.io/record-tuple-laboratory/ I made this 4 years ago. we just keep having this same exact conversation |
| 20:42 | <Richard Gibson> | this is probably the crux: I don't think it's important that Composite({ x: -0 }) === Composite({ x: 0 }), and haven't determined whether or not I'm opposed to it. Composite is in some sense key normalization that sits outside of Map and Set, and normalizing primitives into equivalence classes adds a lot of friction to recovery where the distinction matters (which tends to matter rarely, but not never) |
| 20:42 | <Richard Gibson> | we keep having the conversation because no proposal has ever made it far enough for a satisfactory resolution |
| 20:43 | <ljharb> | fwiw i fail to understand any unordered use cases; in all cases i'd want to preserve the order (talking about async helpers, not composite) |
| 20:44 | <Ashley Claymore> | there is no order here, it's key value pairs |
| 20:44 | <Ashley Claymore> | And the keys are sorted (no symbol keys allowed) |
| 20:45 | <Ashley Claymore> | ah sorry, though you were talking Keys |
| 20:46 | <Ashley Claymore> | Fair. this is why I wanted to resolve the interning question first. so that we can have a discussion on -0 with that decision already made |
| 20:46 | <Ashley Claymore> | as they are related |
| 20:46 | <Richard Gibson> | there are tons of cases where order doesn't matter. For a simple one: perform some operation on every file in a directory |
| 20:46 | <bakkot> | I definitely agree that you usually you want to preserve order. but there are cases where you might not: any time you use .some() on the result, or .reduce((a, b) => a+b), or similar. in those cases it literally does not matter what order things resolve in, and so you just want to get the answer asap |
| 20:47 | <bakkot> | (well, quibbles around commutativity of floating point addition aside) |
| 20:47 | <James M Snell> | sorry, still stuck on the
These are definitively two separate sources, but they end up resulting in the same Composite? That feels... off. When we're talking about options bags / object literals that are passed in, it seems ok, but for stuff like this... treating them as equivalent feels ... wrong |
| 20:47 | <James M Snell> | Btw, I'm intentionally ignoring stack in that as it's just a quick example |
| 20:50 | <bakkot> | or, say you want to fetch a bunch of files, but the files have associated filenames, like [{ name, url }, { name, url }]; you ultimately want a
|
| 20:52 | <bakkot> | you can get results strictly faster with unordered map/buffered in that example, because then you are always fetching three results at once; when order is enforced, you can't drain later results from the buffer until earlier ones have settled, so you're often blocked waiting for earlier ones to settle. there's a nice animation of this in actually on https://github.com/tc39/proposal-unordered-async-iterator-helpers |
| 20:56 | <hax (HE Shi-Jun)> | so I expect [1, x?, 2] could give u [1, 2] if x is undefined 😉 |
| 20:58 | <Richard Gibson> | they don't seem very related to me, but I nonetheless support resolving interning vs. not first and would have done it the same way |
| 21:05 | <ljharb> | ok but that's a single operation on a directory. if i had a list of the files, absolutely the order should be preserved |
| 21:25 | <Richard Gibson> | counting occurrences of a particular search term is not a use case in which order matters |
| 21:26 | <ljharb> | ok - in any of the cases where you can discard order, does it matter if order is preserved? (like, could you use the order-preserving ones and it'd be the same) |
| 21:27 | <bakkot> | depends on whether you care about how long things take |
| 21:28 | <bakkot> | you will ~always get the same result, it'll just take longer |
| 21:28 | <ljharb> | why would that be relevant? preserving order doesn't mean the operations can't all be done in parallel/concurrently |
| 21:28 | <ljharb> | it just means the final results need to be in order |
| 21:28 | <ljharb> | eg the way Promise.all works |
| 21:29 | <bakkot> | generally speaking you want to have a bounded degree of concurrency and also a bounded buffer size |
| 21:29 | <bakkot> | if you drop one of those requirements then yes you can just do everything at once and keep it all in memory |
| 21:32 | <ljharb> | the final result has to be all in memory anyways tho, right? so why can't that be built up out of order (in the proper order) as results settle? |
| 21:33 | <bakkot> | no, there are lots of cases where the final result does not have to all be in memory at once? |
| 21:37 | <ljharb> | ah k because it's an async iterator |
| 21:38 | <ljharb> | ok so with the unordered one, it's just a totally nondeterministic order in the result? |
| 21:38 | <bakkot> | yup |
| 23:31 | <Richard Gibson> | here's an example of exactly that pattern, which I think of as a work pool: https://gist.github.com/gibson042/d8ab425d133400959bc5bb875898a97b#file-github-fixer-user-js-L1023-L1029 concurrency ramps up to the specified |