00:46
<ljharb>
sure, i can unarchive it if you want to comment it yourself? just lmk, or if we can't coordinate, i can
00:47
<ljharb>
also, what happened with the "set method argument internal slot" discussion? i had to drop off
03:32
<bakkot>
ljharb: if you want to unarchive tonight and ping me I'll link it, or you should feel free to do so on my behalf
03:33
<bakkot>
also, what happened with the "set method argument internal slot" discussion? i had to drop off
tl;dr was, for the argument to Set.prototype.union, MM does not like the idea of only reaching in to the [[SetData]] internal slot, but would potentially be OK with reaching in to that slot if present and otherwise falling back to the publicly exposed methods (has, etc), even though this would technically be a violation of proxy transparency (because for most cases it would still Just Work)
03:33
<ljharb>
bakkot: done now, go nuts
03:34
<ljharb>
bakkot: but the receiver would still have slot access only, yes?
03:34
<bakkot>
yeah, receiver is fine
03:34
<bakkot>
that's already how it works
03:34
<ljharb>
awesome
03:34
<bakkot>
that is, it is already common to access the internal slot of the receiver
03:34
<ljharb>
(re-archived the repo, after your comment; lmk if you need anything else)
03:35
<bakkot>
nope, that was all, thanks
03:35
<bakkot>
though in both the argument case and the receiver case we need to figure out what affordances, if any, we're going to make for subclasses
16:38
<Jack Works>
hi I've read the meeting logs yesterday. I'm interested in reviewing structured clone algr. (cc syg )
16:40
<shu>
Jack Works: sure, will request your review when the draft is ready
16:49
<Jack Works>
👀 I set an alarm on 14:00 PST and not be able to present enum before 14:00 cause I'm sleeping.
16:52
<Rob Palmer>
Jack Works: we'll schedule you as late as possible today to help you sleep - so in between 14:00-15:00 PST
17:57
<Rob Palmer>
This is your 4 minute warning: Plenary starts soon
18:02
<ptomato>
I can't do notes right away, but I can in a bit
18:03
<Rob Palmer>
thanks ptomato
18:27
<bakkot>
Mathieu Hofman: can you confirm the conclusion we have captured in the notes is correct
18:27
<bakkot>
I think so but best to confirm
18:44
<bakkot>
never GCing is always legal, so XS's implementation would still be conformant
19:00
<ljharb>
+1, that was a mistake
19:00
<ljharb>
(but i also think registered symbols themselves are a mistake)
19:04
<shu>
what was the original use case for them anyway
19:05
<bakkot>
for registered symbols?
19:05
<bakkot>
it lets you do the same thing as well-known symbols for libraries
19:05
<bakkot>
so a library can interop with itself across realms
19:05
<shu>
but... what realms
19:05
<shu>
we didn't have sync realms
19:05
<bakkot>
iframes
19:05
<bakkot>
we have always had iframes
19:05
<shu>
oh, i guess sync iframes
19:05
<bakkot>
but also, not necessarily across realms
19:05
<shu>
i have a hard time believing that was a use case tc39 cared about back then?
19:05
<bakkot>
just multiple version of the library within the same realm
19:06
<yulia>
Ashley Claymore: can you share with me the test case you used to identify SM gc behavior?
19:06
<shu>
yes, right, coordination without having the library do the heavy lifting
19:07
<Ashley Claymore>
Ashley Claymore: can you share with me the test case you used to identify SM gc behavior?
will do :)
19:07
<bakkot>
well, also lets consumers of a library coordinate with it without having a direct reference to it
19:07
<bakkot>
like you can have a library which defines a protocol, and have someone else implement that protocol without reference to the library
19:09
<ljharb>
shu: as much as i claim that the existence of iframes means browsers can't pretend realms don't exist, i completely agree with you that it's unlikely that was the motivation
19:09
<ljharb>
making the symbol registry realm-specific would have supported the library use case just fine, i think
19:09
<bakkot>
forget I said realms; "multiple versions of a library" is the right thing to think about, whether that's cross-realm or not
19:09
<bakkot>
and consumers of a library which aren't including it directly
19:10
<ljharb>
erights: what if i had a WeakMap of a symbol to an object, and then a WeakRef of the same object, and no other refs to the object. couldn't i observe the collection of the symbol via the collection of the object?
19:19
<Richard Gibson>
subclassing doesn't work anyway, because of e.g. BaseClass[method].call(subclassInstance, …) directly interacting with internal slots
19:20
<bakkot>
ljharb: fwiw I don't think "registered symbols" and "unique symbols" are actually the same kind of thing for users
19:20
<bakkot>
they have the same type but they really do not come up in the same cases
19:21
<Ben Newman (Apollo, @benjamn on GH)>
Aren't registered Symbols often used for safely branding objects, in case you have more the one copy of a library (so instanceof is risky)?
19:21
<bakkot>
Ben Newman (Apollo, @benjamn on GH): yes, but that's not like a regular symbol
19:22
<Ben Newman (Apollo, @benjamn on GH)>
I was responding to the question about whether anyone actually uses registered Symbols
19:22
<bakkot>
ah, sure
19:22
<ljharb>
that's the common appropriate use case, yeah
19:24
<Ben Newman (Apollo, @benjamn on GH)>
"eternal" seems to mean/imply "recoverable after all references are lost"
19:24
<bakkot>
Ben Newman (Apollo, @benjamn on GH): my claim is that there is no good reason to want to put such a brand in a WeakMap
19:24
<bakkot>
at least not that i can think of offhand
19:25
<Ben Newman (Apollo, @benjamn on GH)>
I'm uncomfortable with throwing "good" around like that
19:25
<ljharb>
hm, i just got kicked off the call
19:25
<nicolo-ribaudo>
Me too
19:25
<ljharb>
what did i miss?
19:26
<ljharb>
ah k
19:26
<rickbutton>
did the call just die?
19:26
<ptomato>
same
19:26
<Ben Newman (Apollo, @benjamn on GH)>
(it's back if you reconnect)
19:27
<nicolo-ribaudo>
* Oh again
19:30
<bakkot>
let me put it a different way: the reason to want a registered symbol is that you want something which lives forever, for e.g. coordination across instances/consumers of a library. the reason you want to put symbols in a weakmap is that you want to hold something weakly - you have a symbol which is ephemeral, and you want to hold something else ephemerally. (Otherwise you could just use a Map.) these uses are directly opposed.
19:31
<Ben Newman (Apollo, @benjamn on GH)>
I'm fine with never collecting registered symbols, as are V8 and Mozilla's engine, it sounds like
19:31
<Ben Newman (Apollo, @benjamn on GH)>
they are, quite literally, always reachable once registered, even if you've lost all references
19:32
<rbuckton>
bakkot: But you can also intentionally craft an object that should live forever and also place it in a weakmap. If you want to track a symbol in a way that is "preferrably" weak, you would have to write a lot of defensive code and have both a WeakMap and a Map.
19:33
<Ben Newman (Apollo, @benjamn on GH)>
the "optimization" of collecting registered symbols anyway and then later returning a new reference if someone asks for them again with Symbol.for is simply unsound, IMO
19:33
<Ben Newman (Apollo, @benjamn on GH)>
it would be nice to hear about a JS engine that actually does that
19:34
<Ben Newman (Apollo, @benjamn on GH)>
is someone from Apple on the call? msaboff ? does Safari actually collect registered symbols?
19:34
<bakkot>
rbuckton: my position is that the desire to weakly hold symbols which may or may not be registered is niche enough that it's sufficient for the language to support it without needing to make it trivial
19:34
<yulia>
Robin Ricard: can you add "no preference, general support"?
19:35
<Ben Newman (Apollo, @benjamn on GH)>
XS does not collect any symbols (just stated by Peter H on the call)
19:35
<yulia>
i mean, i care, i like it
19:35
<rickbutton>
yulia: "indifferent"
19:35
<shu>
Ben Newman (Apollo, @benjamn on GH): why is that unsound?
19:35
<Robin Ricard>
changed wording
19:35
<shu>
it happens with strings all the time
19:35
<shu>
and doubles, actually, in V8
19:36
<Ben Newman (Apollo, @benjamn on GH)>
@shu because the reference shouldn't change in any semantically observable way, and the WeakMap question provides observability
19:36
<Ben Newman (Apollo, @benjamn on GH)>
strings aren't stored in [weak]maps/sets by reference though
19:36
<Ben Newman (Apollo, @benjamn on GH)>
they're stored by value
19:36
<bakkot>
it's only unsound if WeakMaps can hold registered symbols
19:36
<shu>
strings can't be put into weak collections at all
19:36
<bakkot>
(and doing so does not prevent GC of the symbol)
19:36
<shu>
correct
19:37
<Ben Newman (Apollo, @benjamn on GH)>
I'm saying it's unsound anyway, and this is just the first gotcha we've encountered
19:37
<shu>
it's... not unsound anyway
19:37
<Ben Newman (Apollo, @benjamn on GH)>
it's sound by accident, currently
19:37
<bakkot>
wasn't an accident
19:37
<bakkot>
that was definitely an intentional part of the design of the symbol registry
19:38
<shu>
yes, they were accidentally eternal before, when it was keyed by something else, like parse nodes or something?
19:38
<yulia>
i have the screenshots if anyone needs them
19:38
<bakkot>
you're thinking of template tags
19:38
<yulia>
cc Robin Ricard
19:38
<shu>
oh, i am, bakkot correct
19:41
<Robin Ricard>
thanks yulia, nicolo took them as well so we are good
19:43
<Ben Newman (Apollo, @benjamn on GH)>
catch the positive names?
19:50
<TabAtkins>
shu: "We should do the same as others" isn't quite the point, "everyone else is doing something useful that we aren't" is.
19:52
<shu>
TabAtkins: can i cast your own spell on you? isn't that a fully general argument for stdlib differences?
19:53
<TabAtkins>
No, it's just pointing out that the argument wasn't just "we should be following the bandwagon".
19:53
<shu>
ah
19:53
<shu>
i was responding to the narrower motivating story of "even surma got tripped up"
19:54
<TabAtkins>
The other lang's version of split() is just genuinely better, which is definitely part of why JS's version is so confusing. ^_^
19:54
<shu>
which isn't the same as "i wish we had this other behavior but we don't", it's "i thought our behavior was X but it wasn't"
19:54
<bakkot>
I will try to remember to ask my dad about the history of this next time I talk to him
19:55
<TabAtkins>
Insofar as Surma would have been confused by Python (off-by-one versus the other langs), sure. But it's not clear from his particular complaint whether it was just familiarity or "i'm confused this works differently entirely"
19:55
<leobalter>
I'd like if we rename this splitn to String.prototype.part (or parts)
20:00
<TabAtkins>
Going for the {remainder: true} part and taking on Python's numbering semantics would defuse these complains, I think.
20:00
<Michael Ficarra>
I have an open issue about the possible confusion that Chip is talking about: https://github.com/lucacasonato/proposal-reversible-string-split/issues/6
20:01
<TabAtkins>
So .split(..., 2, ...) always returns 2 bits between separators, you just get a third item with the remainder if you pass the flag.
20:02
<bakkot>

taking on Python's numbering semantics would defuse these complains, I think

20:02
<bakkot>
say more about what that means?
20:05
<ljharb>
Luca Casonato: i invited you to tc39-transfer on github, so you can bounce your proposal repo there
20:06
<TabAtkins>
bakkot: Well there was an "and" there that was pretty important.
20:06
<TabAtkins>
The part after the "and" that you quoted was more of an "(and, in effect, ...)"
20:07
<Luca Casonato>
I'd like if we rename this splitn to String.prototype.part (or parts)
Could you add that to this issue? https://github.com/lucacasonato/proposal-reversible-string-split/issues/6
20:08
<TabAtkins>
But basically, if we use a flag argument in .split(), I don't think we should significantly change the existing semantics/behavior of the proposal. .split(..., 2) should still always trigger (up to) 2 splits, with the option just controlling whether we get the remainder or not included in the array.
20:08
<TabAtkins>
That also avoids all the regex questions, since the answer remains "act exactly as normal, just include the remainder as a final item"
20:11
<Surma>
(sounds like Luca Casonato got stage 1? :D)
20:11
<TabAtkins>
yes
20:12
<Luca Casonato>
(sounds like Luca Casonato got stage 1? :D)
indeed! :D
20:12
<leobalter>
Could you add that to this issue? https://github.com/lucacasonato/proposal-reversible-string-split/issues/6
Done
20:13
<Luca Casonato>
But basically, if we use a flag argument in .split(), I don't think we should significantly change the existing semantics/behavior of the proposal. .split(..., 2) should still always trigger (up to) 2 splits, with the option just controlling whether we get the remainder or not included in the array.
Don't generally disagree with this, I just worry about discoverability here. If people already don't know about the second argument, how will they find a third argument?
20:13
<TabAtkins>
If people already don't know about the second argument, how will they find a completely separate method?
20:13
<Luca Casonato>
If people already don't know about the second argument, how will they find a completely separate method?
Editor autocompletions :-)
20:14
<TabAtkins>
Editors often offer signature suggestions too ^_^
20:16
<Luca Casonato>
That also avoids all the regex questions, since the answer remains "act exactly as normal, just include the remainder as a final item"
This brings up the question of what our actual definition of N is - is it number of splits, or number of return values? If we continue expanding regexps capturing groups into the return values array when "remainder" is included, then we must use number of return values (non python behaviour). I'd argue that if we overload, then the overload should not support regexp separators.
20:17
<Luca Casonato>
Really, I dislike this capturing group expansion into the return value array behaviour. It significantly increases complexity
20:19
<TabAtkins>
Ugggggh I didn't realize the N is literally "length of the returned array" even when regex capture groups are used, that's worthless.
20:19
<ljharb>
does anyone know who https://github.com/phohensee is, in relation to tc39?
20:22
<Luca Casonato>
Ugggggh I didn't realize the N is literally "length of the returned array" even when regex capture groups are used, that's worthless.
You understand my frustration now? xD
20:23
<HE Shi-Jun>
Yeah the currrent split behavior is just useless
20:23
<Luca Casonato>
"".split(sep, n) === "".split(sep).slice(0, n)
20:24
<ljharb>
for n >= 0, i presume
20:25
<HE Shi-Jun>
I think we'd better follow rust, splitN(n, s) seems very clear and won't confused with the old one.
20:25
<Luca Casonato>
for n >= 0, i presume
> "a|b|c|d|e".split("|", -1)
[ "a", "b", "c", "d", "e" ]
20:30
<Michael Ficarra>
🎉 we just got consensus at the UTC meeting for my proposal to stabilise the spelling of property names/values/aliases! https://www.unicode.org/L2/L2022/22029-canonical-prop-spelling.pdf
20:31
<Michael Ficarra>
we'll be able to delete tables 67 and 68 now
20:41
<Ben Newman (Apollo, @benjamn on GH)>
* table 69 is breathing a sigh of relief right now
20:59
<Rob Palmer>
the meeting resumes in 60 seconds
21:03
<bakkot>
Michael Ficarra: did you ask them why they recommended loose matching in the first place?
21:03
<Michael Ficarra>
no, though it sounded like it's just kind of their default position
21:03
<bakkot>
weird
21:04
<Michael Ficarra>
someone was like "can't we just make it stable but not add it to our stability policy?" and I was like "uuuhhhh that's not gonna work" lol
21:05
<Michael Ficarra>
imo the only reason they would want that is if they planned to break it
21:07
<Michael Ficarra>
why do we allow this form in any position other than the target of a call? do we really want it being passed around?
21:08
<ljharb>
which form?
21:08
<Michael Ficarra>
class.hasInstance
21:08
<ljharb>
i assume class.hasInstance() would work just like super() and import(), in that it's not a real function and can't be passed around
21:08
<Michael Ficarra>
exactly, that's what I'm suggesting
21:09
<ljharb>
oh, is this proposal currently suggesting something different?
21:09
<Michael Ficarra>
that's how I understood the presenter, maybe not?
21:09
<Michael Ficarra>
I didn't look at spec text for this proposal
21:10
<ljharb>
for stage 2 i'd definitely insist alongside you that that's how it work ¯\_(ツ)_/¯
21:10
<Michael Ficarra>
oh no your arm
21:10
<ljharb>
lol matrix is even worse than github with the markdown escaping (-‸ლ)
21:12
<shu>
i thought it was a meta property call syntax, yeah
21:12
<shu>
how do you even pass it around
21:13
<ljharb>
if it were a real function you could foo(class.hasInstance) but obv that'd be subpar
21:13
<shu>
well right, but i didn't think it was a real function
21:15
<Michael Ficarra>
someone want to ask the clarifying question? I'm kinda in the middle of my lunch
21:15
<bakkot>
i am not so convinced by the "you might end up with a partially constructed instance" problem. you have to work pretty hard to still end up with a reference to the instance when a field initializer throws; it's not something you're going to end up passing around natuarlly
21:16
<shu>
i am not understanding what is being proposed for the synthetic brand he wants class.hasInstance to make
21:16
<shu>
at the start or at the end?
21:16
<shu>
oh here's the slide
21:17
<bakkot>
"after the constructor returns" does seem like the right answer to this question to me yeah
21:18
<Michael Ficarra>
oh THAT'S what he meant by function-like
21:19
<bakkot>
does import() work inside eval?
21:19
<bakkot>
i am guessing yes, so I would say this should also work
21:19
<bakkot>
"if there is a direct eval you have to do a lot more work" is not a new thing
21:20
<shu>
super does
21:20
<shu>
and super already causes extra allocation for home objects
21:20
<shu>
so eval also causes that
21:20
<shu>
it's no worse than the status quo, which remains "don't use eval"
21:21
<ljharb>
private fields don't tho, i think
21:21
<shu>
really?
21:21
<ljharb>
~hm, let me confirm~ no nvm, they do work
21:21
<rbuckton>
I need to draft an update on class access expressions. Assuming it will ever move forward, I think I need to move it to a meta property like class.static or something.
21:22
<rbuckton>
or class.constructor maybe.
21:22
<ptomato>
I think we're managing just adequately with the notes, so no need to interrupt the presentation to ask for more, but if someone wants to help out I'd nonetheless appreciate it
21:23
<Michael Ficarra>
rbuckton: for anonymous classes or something?
21:25
<rbuckton>
Its original intent was to handle multiple things: anonymous classes and giving a consistent name to access statics to avoid the this.#foo footgun in static methods
21:26
<ljharb>
it's also nice to avoid repeating the class name - gives you only one thing to change if you want to rename a class declaration
21:26
<Rob Palmer>
so hasInstance() true implies the object was fully constructed without throwing
21:27
<shu>
all non-throwing cases can be, yes
21:27
<Michael Ficarra>
then why did we even expose an immutable binding inside the class? I'm unconvinced
21:27
<shu>
21:28
<shu>
what the hell
21:28
<shu>
i typed ~~~~recursion~~~~~~
21:28
<ljharb>
yulia: i do agree that class.hasInstance is always equivalent to a private field at the end of the class body, that's assigned to sentinel at the end of the constructor, and doing #field in o && o.#field === sentinel
21:28
<shu>
it's not equivalent to private field at the end, is it?
21:28
<Rob Palmer>
to replicate this feature with ergonomic brand checks, i think the user would have to assign to the brand field at each return point in the constructor
21:28
<shu>
yeah, it's like that, but that's also why i'm kinda not super convinced
21:28
<ljharb>
Michael Ficarra good question. i don't find that binding immutability particularly useful, and would have preferred a class access expression
21:28
<shu>
but i'll ask during my queue item
21:29
<rbuckton>
then why did we even expose an immutable binding inside the class? I'm unconvinced
The immutable binding in the class has been a problem for decorators and is constantly surprising to some
21:29
<Michael Ficarra>
ljharb: I think it speaks to the intentions of the delegates who designed the class proposal
21:29
<bakkot>
to replicate this feature with ergonomic brand checks, i think the user would have to assign to the brand field at each return point in the constructor
only because someone might get a hold of this during the constructor even if the constructor throws, right? if you assume the ctor/initializers don't throw it's equivalent?
21:29
<Ashley Claymore>
https://github.com/tc39/proposal-class-brand-check/issues/6#issuecomment-1015303592
21:30
<shu>
Michael Ficarra: it might be just copying the function scope
21:30
<shu>
named function expressions also have that constant lambda scope
21:30
<ljharb>
Michael Ficarra: since some of them are here, it'd be nice to hear their intentions spoken instead of inferring them
21:30
<ljharb>
but yes, it's the same way functions work
21:30
<bakkot>
if so, as I said above, i am not so convinced by the "you might end up with a partially constructed instance" problem
21:30
<Michael Ficarra>
shu: they have their own name scope, but it's mutable
21:30
<ljharb>
which fwiw is also subpar, it's just that self-recursion is much rarer than accessing the constructor's statics
21:30
<shu>
what
21:30
<shu>
the lambda scope is not mutable
21:30
<Michael Ficarra>
uhh I thought it was, let me check
21:31
<bakkot>
nope
21:31
<bakkot>
you can assign to it but it's immutable
21:31
<shu>
yeah
21:31
<bakkot>
it's a unique kind of const among bindings in JS
21:31
<shu>
it's not the same semantics as const but it's immutable
21:31
<shu>
so good
21:31
<Michael Ficarra>
ugh weird
21:31
<bakkot>
which fwiw is also subpar, it's just that self-recursion is much rarer than accessing the constructor's statics
doubt
21:32
<ljharb>
doubt which, the rarity?
21:32
<Rob Palmer>
only because someone might get a hold of this during the constructor even if the constructor throws, right? if you assume the ctor/initializers don't throw it's equivalent?
yes. maybe the constructor passes this to a static function that contains hasInstance from the same class
21:32
<shu>
ugh weird
sickos.jpg
21:32
<bakkot>
yes
21:32
<ljharb>
bakkot: recursion at all is rare in JS due to a lack of PTC
21:32
<ljharb>
it's certainly all over the place, but way rarer than it would be otherwise
21:32
<shu>
...false?
21:32
<bakkot>
this has not been my experience
21:32
<ljharb>
perhaps we look at very different kinds of codebases
21:32
<shu>
most useful recursion i argue is in fact not tail recursion
21:33
<Michael Ficarra>
everything is tail recursion
21:33
<waldemar>
How does Contains work with respect to a class? If you're evaluating Contains on Expr which contains a class expression C, does Contains peek into C's computed property names?
21:33
<Justin Ridgewell>
I do recursion all the time.
21:33
<bakkot>
waldemar: there is a special ComputedPropertyContains which, yes, descends into the computed property names, but not into method bodies or initializers
21:33
<waldemar>
And if you're doing Contains on C itself, does it peek into its own property names?
21:34
<bakkot>
That one I'm not sure of offhand
21:34
<bakkot>
looks like, yes, it peeks into its names but not into method bodies, same as if you called Contains on a parent of C
21:34
<waldemar>
It seems that both do, which is weird. Things in computed property names are both in the class and not in the class?
21:35
<waldemar>
This is a problem for things which are class-scoped like the proposal just presented.
21:35
<bakkot>
We'd need to use a different operation than Contains, yes
21:36
<bakkot>
Contains normally stops at function boundaries - which you can read to mean "places where yield might start meaning a different thing", to be precise - which is why it looks into computed property names but not method bodies
21:37
<waldemar>
Hax's proposal makes it pierce function boundaries
21:37
<waldemar>
Methinks Contains is getting too overloaded and confusing
21:37
<bakkot>
I would suggest introducing a different operation, yes
21:38
<bakkot>
we've done that before - e.g. we have ContainsArguments for looking for arguments in field initializers, which is like Contains but with some details different (it descends into arrows, in particular)
21:38
<bakkot>
adding a similar new ContainsClassHasInstance is probably the way to go
21:48
<HE Shi-Jun>
Currently we use ContainsClassHasInstance to check whether a class scope include any class.hasInstance. Maybe we don't need modify Contains... Speccing such things is too hard for me or TianYang 😂
21:49
<HE Shi-Jun>
Actually we already rewrite the spec text at least three times.
21:52
<HE Shi-Jun>
It seems that both do, which is weird. Things in computed property names are both in the class and not in the class?
We (three champion) haven't has meeting for that, but we chatted about it yesterday and we tend to make class.hasInstance in computed property syntax error. Though as current spec text, (if I really figure out all things) i believe it's allowed but always give u false, because at that time, no one have the class so no instance of it.
21:53
<HE Shi-Jun>
Hax's proposal makes it pierce function boundaries
Yeah, this part make me headache...
21:54
<bakkot>
If you figure out exactly what the semantics you want I'm happy to help with writing the spec text before stage 3
21:54
<bakkot>
at least in terms of telling what is probably the simplest way to write the thing you want
21:54
<bakkot>
important part is figuring out the semantics you want
21:59
<Rick Waldron>
rbuckton: I just realized that I'm going to miss the Enum proposal because I have to go get my kid from school.
22:00
<bakkot>
waldemar: if it's a syntax error [in computed names] then it doesn't refer to either the inner or outer class, because it's unutterable
22:00
<bakkot>
which seems like a good approach to me
22:00
<rbuckton>
rbuckton: I just realized that I'm going to miss the Enum proposal because I have to go get my kid from school.
Is there anything you need me to cover?
22:01
<HE Shi-Jun>
sorry i lose the connection
22:05
<Justin Ridgewell>
~Do we have a link to the slides?~ nevermind, the agenda was updated
22:10
<Mathieu Hofman>
Mathieu Hofman: can you confirm the conclusion we have captured in the notes is correct
Looks right to me. And PR has been updated.
22:11
<Ashley Claymore>
I wonder if we could have a more general construct for 'run this on successful return' . catch for errors, finally for all exits and something else for completed without error
22:11
<Ashley Claymore>
</half-baked-idea>
22:11
<bakkot>
Looks right to me. And PR has been updated.
(pr will need another update; see comment)
22:12
<bakkot>
Ashley Claymore: there's been proposals for like a catch.error meta-property you could use in finally which would tell you what the error is in that case
22:12
<ljharb>
Ashley Claymore: isn't that just "add a statement at the end of the try" tho
22:12
<bakkot>
which lets you just check in your finally if you are in the completed without error case
22:12
<bakkot>
ljharb: end of the try isn't necessarily reached if there's a return
22:13
<ljharb>
(to add that now we'd have to solve it both for syntactic and Promise finally)
22:13
<ljharb>
oh true
22:13
<bakkot>
strong disagree with the claim that any functionality available in syntactic finally must also be available in Promise.finally
22:13
<bakkot>
if that was the claim
22:13
<Ashley Claymore>
Ashley Claymore: isn't that just "add a statement at the end of the try" tho
I set a flag in a catch
22:13
<ljharb>
it was, because in the past that was one of the stated blockers for three-state promises - iow, that claim has precedent.
22:14
<ljharb>
eg the whole catchCancel thing
22:14
<bakkot>
ehhhhhhhhhhhhhhhhhhhhh
22:14
<bakkot>
don't really agree with that on multiple levels
22:14
<ljharb>
none the less, that parity was a huge part of the design of Promise, and it constrained me on Promise finally as well
22:15
<ljharb>
any motivating use case for catch.error would exist in promise finally, so i'm not sure i understand why it'd be ok to do just one
22:15
<bakkot>
you can achieve the same thing with .then and .catch if you really want to
22:16
<ljharb>
you can achieve it with a boolean flag in .catch too.
22:16
<bakkot>
yeah but it's gross
22:16
<bakkot>

so i'm not sure i understand why it'd be ok to do just one

you can add a meta-property to solve it in syntax, and you cannot do that to add it in Promise.finally

22:16
<ljharb>
right, it'd have to be an argument to the finally callback somehow
22:16
<bakkot>
"there is a good way to do this in one case, and not in this other" is a fine reason to provide an affordance in one case and not the other
22:16
<Mathieu Hofman>
Ashley Claymore: there's been proposals for like a catch.error meta-property you could use in finally which would tell you what the error is in that case

I have on my list of wishes to add an optional error argument to the finally block:

try {
 // something that throws
} finally (err) {
  if (err) {
    // something
  } else {
    //something else
  }
}
22:17
<Ashley Claymore>
what if someone throws falsey
22:17
<ljharb>
Mathieu Hofman: you can throw and reject with undefined, so it'd have to be a container
22:17
<yulia>
you can implement this with private fields though, so it is a usecase that is possible with it -- is it being used for that?
22:17
<waldemar>

We (three champion) haven't has meeting for that, but we chatted about it yesterday and we tend to make class.hasInstance in computed property syntax error. Though as current spec text, (if I really figure out all things) i believe it's allowed but always give u false, because at that time, no one have the class so no instance of it.

It's not always false. You can define a function inside a computed property name expression, squirrel it away somewhere, and call it later.

22:17
<Mathieu Hofman>
and allow spread so you can do finally (...errs) { if (errs.length); } to handle the err === undefined case
22:18
<ljharb>
i suppose that'd work in .finally with arguments.length
22:18
<Mathieu Hofman>
And similar for Promise.p.finally
22:18
<Ashley Claymore>
Also there's not a way to only freeze prototype right, though I think there is a proposal for that right?
22:18
<bakkot>
anyway maybe it would prove possible to add an argument to the Promise.finally callback, which would be fine; but if not I don't think that needs to block the syntax
22:19
<bakkot>
Also there's not a way to only freeze prototype right, though I think there is a proposal for that right?
yeah, though I haven't been working on that one lately
22:19
<bakkot>
need to revisit and figure out how to work with the rest of the traps, like [[IsExtensible]]
22:20
<ljharb>
i'd love to see that freeze proto one advance
22:20
<shu>
override mistake be damned?
22:20
<ljharb>
? how does it conflict with that?
22:21
<shu>
oh, maybe i misunderstood what the freeze proto proposal does
22:21
<ljharb>
iiuc it'd make Object.prototype not be exotic, because it'd make "a fully mutable object with a frozen [[Prototype]]" a normally achievable thing
22:22
<shu>
yeah i completely misunderstood, please ignore
22:22
<bakkot>
yeah, just freezes the prototype slot itself, not the prototype object
22:22
<bakkot>
override mistake be damned?
still on board with this though :P
22:22
<ljharb>
lol
22:22
<shu>
if you can get someone else to own the breakage fallout bugs on v8, sure
22:23
<bakkot>
really it's more "be damned" in the sense of "damn it"
22:26
<yulia>
HE Shi-Jun: posted https://github.com/tc39/proposal-class-brand-check/issues/15
22:27
<bakkot>
ljharb: ok, so, syntactic finally is already unlike Promise.prototype.finally, in that syntactic finally can override the return value, and Promise.prototype.finally cannot
22:27
<bakkot>
so how does that not already break the equivalence?
22:28
<HE Shi-Jun>

We (three champion) haven't has meeting for that, but we chatted about it yesterday and we tend to make class.hasInstance in computed property syntax error. Though as current spec text, (if I really figure out all things) i believe it's allowed but always give u false, because at that time, no one have the class so no instance of it.

It's not always false. You can define a function inside a computed property name expression, squirrel it away somewhere, and call it later.

You are correct! so maybe we could keep current behavior 😅
22:30
<bakkot>
I like syntax error, syntax error is good - there is no reason to want to write this code
22:31
<ljharb>
bakkot: you're right, that is the one way it's unlike; and given that it's impossible via the callback, we all decided to accept that difference. but that doesn't mean further avoidable deviation is a good idea
22:32
<bakkot>
sure. depends on how avoidable it is.
22:32
<Michael Ficarra>
I am so incredibly opposed to auto-increment, I cannot find the words to describe it
22:34
<ljharb>
so for the enum proposal - this is a ton of discussion about semantics, which are more of a stage 1+ concern. is it worth a 💩 to talk about problem space/motivation?
22:34
<ljharb>
sorry that's a "point of order" initialism; my autocorrect took over
22:34
<Michael Ficarra>
ljharb: agreed, was thinking the same thing
22:34
<Michael Ficarra>
this is cool and all, but the more important bit is to convince us that there's a problem worth solving here
22:34
<Justin Ridgewell>
Lessons from protobuf are that anything other than explicitly id'd values are a big footgun for cross-module apps
22:34
<rbuckton>
auto-increment gives a meaningful value for enums whose base primitive is number.
22:35
<Mathieu Hofman>
Also there's not a way to only freeze prototype right, though I think there is a proposal for that right?
Yeah unfortunately setImmutablePrototype is tied to preventExtensions right now. I wish it was available
22:35
<bakkot>
https://github.com/tc39/proposal-freeze-prototype
22:36
<bakkot>
(very stale, as mentioned, just the place to follow along or pick up if you want to make it happen)
22:37
<rbuckton>
If you have enum Color of String { Red, Green, Blue }, it can be clear that Color.Red === "Red".
If you have enum Color of Symbol { Red, Green, Blue }, it can be clear that typeof Color.Red === "symbol" and Color.Red.description === "Color.Red".
If you have enum Color of Number { Red, Green, Blue }, what would you expect Color.Red to be?
22:37
<Mathieu Hofman>
if you can get someone else to own the breakage fallout bugs on v8, sure
I have ideas to tame the override mistake beast, but I need to familiarize myself with previous attempts first
22:37
<shu>
good luck
22:39
<yulia>
note takers?
22:40
<Michael Ficarra>
it's unfortunate, I think there actually is a pattern in use in the wild that we could improve by providing language support with something like this, but this proposal hasn't been motivated in that way
22:41
<Michael Ficarra>
I have ideas to tame the override mistake beast, but I need to familiarize myself with previous attempts first
many have come before you; none have succeeded
22:41
<shu>
i am most interested in danielrosenwasser's agenda item
22:41
<shu>
the thing that would convince me is to get rid of a TS wart if TS is willing to do some kind of breaking change, or maybe there's a way to thread the needle, idk
22:42
<Mathieu Hofman>
bakkot: you're right, that is the one way it's unlike; and given that it's impossible via the callback, we all decided to accept that difference. but that doesn't mean further avoidable deviation is a good idea
At least errors thrown or a rejected promise returned in a finally is taken into consideration. I think return or break being control flow justify the difference
22:42
<Rob Palmer>
do we accept that creating a set of unique values for a given type is a frequent use-case? I hope the answer is yes - it feels self-evident based on other languages and my personal usage.
22:42
<shu>
sure, but it's not an expressivity gap
22:43
<Michael Ficarra>
shu: but I bet there's some value in normalising how that's done
22:43
<Rob Palmer>
it's not succinct enough with the current constructs imo - it becomes imperative rather than declarative
22:43
<Michael Ficarra>
right now people just do one of a dozen different approaches, and it makes code more accessible if everyone is using built-in support
22:43
<ljharb>
i agree on the "coordination" angle for what is currently "a set of strings/symbols/numbers"
22:44
<shu>
that feels very hopeful
22:44
<shu>
i am not that full of hope
22:44
<ljharb>
but i hope we're all on the same page about that motivation before we debate syntax, is all :-)
22:44
<Michael Ficarra>
Rob Palmer: not just succinct, but it would be nice if, for example, all of the value-back-to-enum-name functions had the same name
22:44
<shu>
but i'm not against it! i just want TS to convince me
22:44
<shu>
TS has the biggest lever here in pushing folks to one way
22:44
<rbuckton>

I'm interested in formalizing a syntax for a common pattern, just as class formalized syntax for class behaviors previously built using function. This also affords a number of opportunities:

  • declaration on which to hang decorators for metadata/serialization/etc.
  • possibility of leveraging constant inlining vs a regular object.
  • possibility of extending to ADT enums with support for pattern matching/destructuring.
  • improved API for working with enum-like types, such as reverse mapping (value -> key) without the conflicts TS string enums currently have.
22:45
<Justin Ridgewell>
Can we just fix TS's utterly-terrible-no-good-very-bad output for enums?
22:45
<Michael Ficarra>
yeah we're going to have to get past that unfortunate typescript cowpath
22:46
<Rob Palmer>

Mark is now talking about string unions

type foo = "this" | "that"

22:46
<shu>
people use string literals as enums, don't they
22:47
<ljharb>
sure but it's nice if they have identity, so Foo.A and Bar.A aren't accidentally interchangeable
22:47
<rbuckton>
You won't necessarily get that for string based enums, but you would for symbol-based ones.
22:47
<shu>
i definitely do not want to solve the ADT use case in the same proposal
22:47
<shu>
that seems like a very very different problem space to me
22:47
<TabAtkins>
Yeah, identity (aka enum members are primitives or similar) is pretty important
22:47
<ljharb>
(i'm still bitter from realizing that scala inherits java's silly confusion of chars and ints)
22:47
<TabAtkins>
and agree that ADT should be put to the side
22:47
<Michael Ficarra>
shu: yes exactly, people use literally dozens of different approaches, and it would be nice if there was more consistency there
22:48
<Michael Ficarra>
ljharb: Scala inherits so much of Java's crap, it really kills the language for me
22:48
<shu>
Michael Ficarra: i dislike TC39 being looked to as a lever to move the ecosystem one way
22:48
<ljharb>
shu: re your queue item, class DID actually kill off almost all of the other ways of doing inheritance (including Object.create, not that it was very popular before that)
22:48
<Michael Ficarra>
shu: how is it a lever? people are already doing this all over the place
22:48
<ljharb>
so i think an enum syntax would actually achieve that goal.
22:48
<shu>
i should qualify, where there is no expressivity gap
22:49
<shu>
ljharb: class is good historical evidence, yes
22:49
<shu>
thanks
22:50
<bakkot>
can't typescript just introduce like a // ts-enum annotation which means "this is an enum"
22:50
<bakkot>
without needing new syntax in the language
22:50
<bakkot>
I am confused by this whole "it helps static analysis" claim
22:50
<ljharb>
"eslint, for normal JS" is still a pretty important use case
22:51
<bakkot>
not clear how much this would help eslint?
22:51
<ljharb>
targeting parseable grammar (that comes with semantics) is far easier than trying to target a nonstandard commenting convention?
22:51
<rbuckton>
There's already a /** @enum {T} */ in JSDoc, but it does not mean what most people want it to mean, unfortunately.
22:52
<shu>
shu: how is it a lever? people are already doing this all over the place
people aren't doing the same thing all over the place, they're doing the same kind of thing all over the place
22:52
<bakkot>
ljharb: if TS wrote down a specific syntax for the comment then it would not be any harder to target
22:52
<ljharb>
sure but then it'd still be TS-specific, and that's not relevant to non-TS users.
22:52
<Jack Works>
oh sorry I got offlined
22:52
<Jack Works>
let me rejoin
22:53
<bakkot>
ljharb: I was responding specifically to the "it helps TS analysis" motivation
22:53
<bakkot>
if there is some other motivation, sure
22:53
<ljharb>
ah ok, gotcha
22:53
<rbuckton>
I'm interested in whether ADT enums could help avoid megamorphic ICs in V8, where the "kind" of enum value could be inlined into the IC, so that switching/matching on the enum kind wouldn't be polymorphic.
22:54
<Jack Works>
hmm can anyone hear me?
22:55
<Jack Works>
looks like still not got online 🤔
22:55
<rbuckton>
The TS compiler has a lot of switch (node.kind) { ... } cases that are megamorphic. Being able to do something like match (node) { when (Node.Identifier) -> ... } and avoid megamorphism would (hopefully) be a perf win.
22:55
<Michael Ficarra>
exhaustiveness checks will be a godsend, too
22:56
<rbuckton>
Jack Works: Are you able to hear and just can't speak?
22:57
<shu>
rbuckton: not sure why a tagged disjoint union "kind", even if built-in, would make a callsite any less megamorphic
22:57
<shu>
presumably each disjunct of the sum type still has a different layout
22:57
<shu>
if the callsite deals with those different layouts, it's as polymorphic as before
22:59
<Justin Ridgewell>
Can anyone give a demonstration of ADT without using pattern matching?
22:59
<bakkot>
Justin Ridgewell: every AST
23:00
<bakkot>
the usefulness for ASTs means people who work with programming languages tend to think about them a lot
23:00
<TabAtkins>
what's the point of adt without pattern matching
23:00
<TabAtkins>
(i say, as one of the pattern-matching champions)
23:00
<shu>
my hot take is i think PL and compiler people definitely overindex on pattern matching and ADTs
23:01
<shu>
like ML is a great language for writing ML compilers
23:01
<Justin Ridgewell>
A code example. I don't understand how I get fields out of a Square(int int)
23:01
<rbuckton>
Yes, but I'm curious if it would be possible to inline a consistent shared field such as a "kind" that all ADT enum values would be expected to have, so that the switch isn't megamorphic when branching on that "kind". Each branch could then be monomorphic.
23:01
<Justin Ridgewell>
The only thing that demonstrates is pattern matching, and I hate pattern matching.
23:02
<Ben Newman (Apollo, @benjamn on GH)>
hate? wow
23:02
<bakkot>
Justin Ridgewell: normally you name the fields I think
23:02
<rbuckton>
Justin Ridgewell: You mean like, square[0]?
23:02
<Justin Ridgewell>
Is it an object?
23:02
<rbuckton>
An ADT enum would likely be a record or tuple value, if not a regular object. That's still TBD.
23:03
<gkz>
Flow Enums: https://flow.org/en/docs/enums/ Comparison with TS and Flow Enums: https://medium.com/flow-type/typescript-enums-vs-flow-enums-83da2ca4a9b4
23:03
<bakkot>
gkz: your mic is still hot I think
23:03
<bakkot>
oh fixed nvm
23:03
<Michael Ficarra>
there's a common pattern in Haskell libraries, considered a good practice, to actually not expose your ADT constructors so that consumers can't pattern match on them
23:03
<Michael Ficarra>
the idea is you're supposed to do every kind of pattern matching that makes sense as functions and expose those
23:04
<Michael Ficarra>
also allows for "smart constructors"
23:04
<bakkot>
lol I just did exactly the same thing as PH: https://github.com/Z3Prover/z3/blob/c6539deb6169afde2b569fba89a828f2f726691f/src/api/js/scripts/parse-api.js#L141-L204
23:04
<bakkot>
converts C enums to TS
23:06
<shu>
Justin Ridgewell: i don't think you can get fields out of an ADT without pattern matching. the point of ADTs is that you have N type constructors to create the data, and on the other side you have "eliminators" to unwrap the type constructors and get the field out. usually, the eliminator is pattern matching
23:07
<shu>
on the flipside, you have classes, which sometimes get described as co-data, where eliminators (methods) are how you define the class to begin with
23:09
<shu>
but that part of my life is behind me now, never to be thought of again
23:09
<bakkot>
have a hard time imagining going from that to C++
23:10
<Michael Ficarra>
waldemar: I would describe the problem as "there is a common need today to create related but distinguishable values, and there's many different ways to do it. language support would unify these strategies, make code more approachable, provide useful info to tooling to do things like exhaustiveness checks, etc"
23:10
<Michael Ficarra>
Justin Ridgewell: you should check out recursion schemes
23:10
<bakkot>
that doesn't cover sum types, though
23:11
<ptomato>
but the intention was not to cover sum types in this proposal, right?
23:11
<bakkot>
ptomato: not clear to me
23:11
<bakkot>
definitely sounded like sum types were in scope?
23:11
<waldemar>
sum types are the interesting part of this proposal, that we can't currently do in the language
23:11
<Michael Ficarra>
the last slide said "simple enums", with other stuff being later
23:12
<waldemar>
but they do have a fairly large impedance mismatch given how dynamically typed ecmascript is
23:12
<rbuckton>
Michael Ficarra: As well as additional affordances that novel syntax would present, such as the possibilities for constant inlining, decorators, serialization, and consistent patterns for construction (and deconstruction) of these values.
23:13
<Michael Ficarra>
I'm frustrated that this couldn't reach stage 1. I think it met the criteria, but just wasn't presented in a way that made that clear, no offense to the champion group.
23:13
<Michael Ficarra>
rbuckton: true
23:14
<Bradford Smith>
I think what's missing here is some clear examples of the current way of doing what enums do are problematic.
23:14
<waldemar>
I'd view the problem more as providing a lightweight statically analyzable way of defining constants in general. But all of the options are biased to do other things by using the enum keyword.
23:14
<Bradford Smith>
s/examples of/examples of how/
23:15
<rbuckton>

For example:

const Color = { Red: 0, Green: 1, Blue: 2 };
const data = { color: Color.Red };
fs.writeFileSync("foo.json", JSON.stringify(data));

Writes {color:0}, which isn't helpful when maintaining config files, while:

const Color = { Red: Symbol(), Green: Symbol(), Blue: Symbol() };
...

Would be safer at runtime, but would fail to stringify.

23:15
<Bradford Smith>
"JavaScript doesn't have enums" isn't a problem all on its own.
23:16
<Bradford Smith>
rbuckton: that's a start. I'd love to see a presentation with at least a half dozen such examples and explanation of what the desired behavior is.
23:17
<Bradford Smith>
and why it's hard with current language constructs to get that behavior
23:17
<sarahghp>

For example:

const Color = { Red: 0, Green: 1, Blue: 2 };
const data = { color: Color.Red };
fs.writeFileSync("foo.json", JSON.stringify(data));

Writes {color:0}, which isn't helpful when maintaining config files, while:

const Color = { Red: Symbol(), Green: Symbol(), Blue: Symbol() };
...

Would be safer at runtime, but would fail to stringify.

How is this not solved by const Color = { Red: 'red' , ..}
23:23
<Jack Works>
😂 I didn't do a good preparation cause I didn't figured out how to express those ideas in my brain. with suggestions and feedbacks from the chat I think it can be better next time. and as I said in the presentation, I'm not push the proposal forward until it has a complete design and proven to has benefit to add it. I agree that frozen object (or TypeScript/flow.js enum) is enough today (mostly?) so my main focus point is on the ADT enum.
23:31
<Rob Palmer>
Jack Works: I'm really pleased you gave this presentation. It seems like there were plenty of people interested in this space. So hopefully they can help you to work more on the use-cases and problem definition. (And I am biased because I love enums)
23:33
<Jack Works>
Thanks 🙏
23:35
<gkz>
Jack Works: I would happy to be involved with this proposal if it's something that's going to be moving forward. We had a lot of discussion with both library authors and end users, and examined many use-cases, in preparation for the design of Flow Enums, and then listened and iterated based off of feedback. But again for us a large amount of the value derived is from when enums are used with the type system.
We have also begun research on an ADT extension to Flow Enums, and have analyzed use-cases and so on.
23:39
<rbuckton>
How is this not solved by const Color = { Red: 'red' , ..}
When I initially started talking about an enum proposal several years ago, a number of current committee members were strongly in favor of symbol-valued enum members for better runtime safety. Yes, you could do { Red: 'red', ... }, but you lose out on that safety.
23:43
<Rob Palmer>
Authenticity of enum values (provided by Symbols) is something folk won't reach for unless it has sweet syntax. And I'd argue it's what you normally want for robustness reasons - no typos.
23:44
<shu>
enums-as-constants is a different problem than ADT enums
23:44
<shu>
until they're separated or more clearly articulated to need to be solved together i'm uncomfortable with the proposal
23:44
<bakkot>
symbols also can't be serialized, so I don't think that helps the "would fail to stringify" case?
23:45
<shu>
even thuogh Jack Works says ADTs are deferred as a follow on it keeps coming up in the discussion so i'm not sure who's interested in solving what
23:48
<Jack Works>
I know rbuckton is interested
23:50
<Jack Works>
Ah I should go sleep first, I'm not clear headed now
23:59
<rbuckton>
shu: Every time I've brought up enums in discussion with other members of plenary or folks in the TS/JS community, ADTs are invariably mentioned as a feature someone would like to see adopted. The use cases frequently overlap, and a number of languages support enums that can be used both for ADT and non-ADT scenarios.