01:12 | <rkirsling> | wait for real? https://github.com/tc39/test262/blob/main/test/built-ins/Error/cause_property.js#L37 |
01:14 | <rkirsling> | why would there be a difference between explicit `undefined` and non-existence in a options bag? |
01:15 | <ljharb> | rkirsling: differentiates between an error with no cause and one with a cause of `undefined`? |
01:16 | <ljharb> | perhaps, a “undefined is not a function” error :-p |
01:16 | <rkirsling> | lol |
01:16 | <rkirsling> | but it's not null |
01:16 | <rkirsling> | like, is there any precedent for that anywhere in the language? |
01:17 | <ljharb> | for presence and undefined being different? tons. it’s the first options bag tho |
01:18 | <rkirsling> | that's the thing |
01:18 | <ljharb> | (non-intl, at least) |
01:18 | <ljharb> | cause, and temporal, and growable array buffer, and import assertions, might be the first options bags, whichever lands first? |
01:19 | <ljharb> | but the way userland treats options bags does often pívot on presence and not undefined, i think |
01:19 | <rkirsling> | that would be shocking to me as a user |
01:20 | <rkirsling> | ...assuming it resulted in anything noticeable |
01:20 | <ljharb> | i mean, in this case if you don’t differentiate between absent and undefined, error.cause will be undefined either way, so I’m not sure why it makes a difference :-) |
01:21 | <rkirsling> | because an implementation has to create a property descriptor for no reason whatsoever |
01:21 | <ljharb> | oh, you’re not talking about “it’s absent when absent” you’re surprised it’s present when undefined? |
01:22 | <ljharb> | the Get is observable |
01:22 | <rkirsling> | yeah like that's a userland object that may have been reused |
01:22 | <ljharb> | it seems strange to me to do the Get and not make the property |
01:22 | <rkirsling> | why would setting to undefined not be equivalent to deletion |
01:22 | <rkirsling> | from the API's standpoint |
01:22 | <ljharb> | because it’s not, for many things? |
01:23 | <ljharb> | anything using Object.keys/entries/values, or object spread/rest, will see the key if it’s present and undefined, but won’t if it’s absent |
01:24 | <rkirsling> | I mean yeah cases of enumeration are supposed to be the time where it makes a difference |
01:24 | <ljharb> | typically you don’t mutate objects either; you make a new one that lacks the property you don’t want |
01:24 | <ljharb> | I’m not sure why someone would mutate but not delete |
01:24 | <rkirsling> | this isn't enumerating anything, it's reaching in and taking action on undefined |
01:24 | <rkirsling> | I mean that was the old norm |
01:24 | <ljharb> | i don’t think it was |
01:24 | <rkirsling> | it absolutely was |
01:24 | <ljharb> | the old norm was delete. Then it shifted to “copy” |
01:25 | <rkirsling> | we have a whole webkit blog post about "hey it's no longer unperformant to use delete" |
01:25 | <ljharb> | the period of time where deletion was thought of as bad and mutation was also not thought of as bad is either very short or nonexistent |
01:25 | <ljharb> | well sure. But long before that came out, mutation fell out of vogue |
01:25 | <ljharb> | that there’s old code that’s now faster is great, but doesn’t affect user expectations imo |
01:26 | <rkirsling> | okay fine but it doesn't have to be a matter of reuse |
01:26 | <ljharb> | iow, that optimization was years too late to actually cause users to like using `delete` |
01:27 | <ljharb> | (here I’m arguing against “setting to undefined as deletion” as a justification; unrelated to whether we care about presence vs undefined or not) |
01:28 | <rkirsling> | if you were simply filtering existing options then you're not expecting setting undefined to do anything |
01:29 | <rkirsling> | `{ foo: originalOptions.foo }` or something |
01:29 | <ljharb> | true. but I’d expect you to spread the original object, and overwrite what you wanted, rather than doing it one at a time |
01:29 | <ljharb> | either way tho, having an own undefined cause property, versus lacking one - when do you expect that to cause a problem for users doing this? |
01:30 | <rkirsling> | I don't, I just see no benefit to having a property descriptor get created |
01:31 | <rkirsling> | if there's not a precedent for it then it seems like taking conscious action where none was desired |
01:31 | <ljharb> | right, so for these users, no difference - but for users who do care, the presence or absence of a property descriptor actually communicates something useful. |
01:33 | <ljharb> | unfortunately since we decided not to do an internal slot and a prototype accessor, this kind of choice comes up, but i think matching own-ness between the options bag and the error instance is actually a useful property. |
01:33 | <rkirsling> | it shouldn't? |
01:33 | <rkirsling> | like, I would call it harmful if it's viewed as not extraneous but usable |
01:33 | <rkirsling> | it's undefined not null |
01:34 | <ljharb> | why is that different |
01:34 | <ljharb> | Both are explicit values |
01:34 | <ljharb> | the belief that one means “explicitly empty” and one means “never set” is decidedly not a universal one (optional chaining and nullish coalescing operate on both, because of this) |
01:35 | <rkirsling> | how are you seeing optional chaining and nullish coalescing as supporting your point |
01:37 | <rkirsling> | it is not expected for mere existence to affect anything but enumeration |
01:37 | <rkirsling> | a user has no other way to opt out using an object literal |
01:39 | <rkirsling> | anyway I am completely exhausted and I didn't come here to debate minutiae; it genuinely seems like a bug, if there's no precedent to say otherwise |
01:39 | <rkirsling> | people code in different ways |
01:39 | <rkirsling> | this is not expected behavior |
01:41 | <ljharb> | i hear that you don't expect it |
01:41 | <ljharb> | i very much do |
01:42 | <ljharb> | i write code all the time that checks hasOwnProperty |
01:48 | <rkirsling> | was this ever pointed out in committee? |
01:48 | <rkirsling> | like, if this is going to be the thing to set the precedent then having such a conversation seems like a stage 4 blocker |
01:50 | <ljharb> | hm, not sure. If we didn’t explicitly decide it tho then i don’t think it could be a binding precedent really. I’ll try and find something in the notes and repo tho |
02:02 | <Bakkot> | https://github.com/tc39/proposal-error-cause/pull/26#discussion_r583989100 |
02:02 | <Bakkot> | rkirsling ljharb ^ |
02:03 | <Bakkot> | and also https://github.com/tc39/proposal-error-cause/issues/2#issuecomment-789375512 |
02:07 | <rkirsling> | wow |
02:07 | <rkirsling> | that is fascinatingly weird |
02:09 | <rkirsling> | but that rationale combined with the knowledge that shu also brought this up makes me somewhat less queasy |
02:10 | <rkirsling> | Bakkot: are you concerned with this setting a precedent for options bag options in general though, even though the motivation here has nothing to do with that? |
02:10 | <Bakkot> | no, not especially |
02:10 | <rkirsling> | okay |
02:12 | <Bakkot> | like temporal has a bunch of options-bag-like cases and they all just do `get` |
02:12 | <Bakkot> | e.g. |
02:13 | <Bakkot> | which is fine, because they are not in the situation that "undefined" is a sensible value for the option to take plus also having different behavior for presence and absence of the option |
02:13 | <Bakkot> | that's a fairly unique scenario |
02:13 | <Bakkot> | I do want it to serve as precedent for options bags which meet both of those criteria, because I think it's the right behavior there |
02:19 | <rkirsling> | hmm |
02:19 | <rkirsling> | can you envision another (hypothetical) example? |
02:22 | <Bakkot> | not off the top of my head |
02:22 | <Bakkot> | most places are at least a little bit typed |
02:23 | <Bakkot> | the only reason this one isn't is because it's intended for use with existing untyped syntax |
02:23 | <Bakkot> | (namely throw/catch) |
02:32 | <rkirsling> | fair enough |
02:32 | <ljharb> | Bakkot: thanks for finding that |
02:34 | <ljharb> | i do agree that when `undefined` isn't a reasonable real value for an option - which will be almost every other case - then it should just do a Get |
15:17 | <shu> | perhaps we should introduce a 3rd nullish type to mean absent |
15:17 | <shu> | we can even name it *empty* so it's easily confused with ~empty~ in the spec |
15:18 | <ljharb> | `nil` |
15:19 | <shu> | will there be a separate concept of "nilish" vs "nullish"? |
15:19 | <shu> | then we can extend the JS trinity diagram |
15:21 | <ljharb> | i am reminded of https://github.com/BrendanEich/nil (which brendan resurrected but didn't create) |
15:22 | <shu> | oh my goodness, that toString semantics |
17:17 | <robpalme> | could I get a blessing here plz https://github.com/tc39/notes/pull/123 |
17:53 | <akirose> | robpalme: i have edits. should I open a PR on your fork's branch? |
17:53 | <akirose> | ljharb: don't merge yet! i mean, you can, but 👆 |
17:55 | <ljharb> | nah was just stamping per request |
17:55 | <ljharb> | i'll let yall handle it |
19:39 | <robpalme> | I won't merge. Please use suggestions on the PR. |