00:01
<waldemar>
If you care about performance, it's a bad idea to feed external input into a deterministic hash function. That invites attacks such as Hash Flooding: https://en.wikipedia.org/wiki/Collision_attack#Hash_flooding
00:40
<littledan>
If you care about performance, it's a bad idea to feed external input into a deterministic hash function. That invites attacks such as Hash Flooding: https://en.wikipedia.org/wiki/Collision_attack#Hash_flooding
V8 rotates its hash function on boot, but at some point there was a security issue where, if you put some Maps in the startup snapshot (which Chrome doesn't do, but Node.js started doing at some point), it forgot to re-rotate them, causing a DOS risk of this form! Fixed by joyee :)
01:49
<littledan>
I am not sure what we're supposed to get from Project Valhalla when primitives/value semantics has already been deemed not an option due to implementability issues
01:51
<littledan>
if the question is class-based vs object-based immutable things, I think there's a lot of interest among JS developers in having easy-to-use mechanisms for immutable data structures that don't force them to write classes
01:54
<rbuckton>
If you care about performance, it's a bad idea to feed external input into a deterministic hash function. That invites attacks such as Hash Flooding: https://en.wikipedia.org/wiki/Collision_attack#Hash_flooding
Performance and/or security. This is one of the reasons why I indicated non-determinism for string hashing between application restarts is a good thing, actually.
01:54
<littledan>
There's a lot of complexity with class-based immutable things, e.g., we'd need to use initializer list-based constructors, so if there's subclassing it's a totally different instantiation protocol. And if they're value types, it's even more complicated if we want to avoid any kind of global registries like we discussed with shared structs.
01:55
<littledan>
Performance and/or security. This is one of the reasons why I indicated non-determinism for string hashing between application restarts is a good thing, actually.
Right so given that it's simultaneously a good thing and a bad thing, we've so far opted to hide the hashcodes
02:00
<rbuckton>
The "bad thing" case (non-determinism being bad) seems so narrowly focused that I have a hard time believing it should apply broadly across the language.
02:04
<bakkot>

I don't know that class-based vs object-based is a meaningful distinction in JS, but in any case, the main things I want to take away are

  • even in a language with a well-established idiom for .equals/.hash, having the ability to conveniently create objects which are == each other is useful
  • they've explored some of the relevant space already, such as having WeakMaps which reject these values (though, this being Java, that behavior is customizable)
02:16
<rbuckton>
The overhead incurred by a CompositeKey just seems like a non-starter to me. Maybe we could find another approach that can overcome these caveats. For example, a Hasher class instead of a global Object.hash(). A given Hasher could just use a monotonically increasing number for each unique object instance it comes across, and maybe is configurable as to how it handles string hashing (e.g., algorithm, randomness, seed values, etc.). Then your equaler looks like { equals(a, b), hash(obj, hasher) } and you then can write new Map([], { equaler, hasher }). Object hashes could be deterministic without being a global communications channel. String hashing can be deterministic if you want it to be, or not, as best fits your scenario.
02:33
<littledan>
yes, I agree that having values which === each other based on their contents is useful; it's unfortunate that we can't go in that direction per implementer feedback, but I think we can still find many related but different improvements to JavaScript.
02:35
<littledan>
Class-based vs object-based is more about syntax and conventions. I think these sorts of features are more likely to have broad adoption with convenient syntax, especially with something related to object/array syntax. If we had class syntax, we'd have to think through how new works. But it's also useful to look at other issues in this area which don't relate to those.
02:36
<littledan>

The overhead incurred by a CompositeKey just seems like a non-starter to me.

If this means one which is interned so that === works, I agree

02:37
<littledan>
The "bad thing" case (non-determinism being bad) seems so narrowly focused that I have a hard time believing it should apply broadly across the language.
How do you think we should work through the situation where a bunch of people directly disagree with this judgement?
02:49
<rbuckton>
That's why I mentioned alternatives in the preceding message. I'm willing to consider alternatives that support the scenarios I've discussed. I'd entertain an opaque hash if it were feasible to actually support those scenarios in a performant way. A Composite key cannot satisfy those scenarios as it can never be fast in a custom collection, only in native Map/Set. Maybe I'd be less concerned if a ConcurrentMap were in the MVP for shared structs, but I know that shared structs without concurrent collections is already a hard sell. The problem is that shared structs without concurrent collections is nearly unusable for my use cases without the ability to implement a fast efficient custom collection. To support it in the dev trial I essentially had to tag every shared struct with a monotonically increasing identity to use as the hash, and implement a string hashing algorithm just so I could roll my own ConcurrentMap. That approach has a lot of overhead I'd rather avoid.
02:53
<rbuckton>
tl;dr, I don't need hash/equals if I know I'll get ConcurrentMap, but I know that's a long shot at this point. I'd still lament the overhead of a CompositeKey, but my primary use cases would be covered. On the other hand, hash/equals means I'm less concerned about when, if ever, I get ConcurrentMap since I could readily implement it in userland.
03:04
<bakkot>
it was not clear to me that this feedback applied to interning at construction time, with a constructor function rather than syntax
03:05
<bakkot>
and being actual typeof "object" objects rather than a new kind of primitive, and so on
03:05
<bakkot>
https://matrixlogs.bakkot.com/TC39_Delegates/2024-04-09#L294-L290
06:06
<eemeli>
Could a hashcode be a global symbol for which Symbol.keyFor() returned undefined? Wouldn't that avoid the concerns about non-determinism?
08:43
<Ashley Claymore>
Symbols don't help non-determinism.

```
typeof hash("a"); // "symbol"

if (hash("a") === hash("b")) {
  print("foo");
}
```
08:43
<Ashley Claymore>
Does the program print foo?
08:47
<Ashley Claymore>
If the spec says that the symbol hash of every value is different and never equal, then we haven't hashed into a smaller space.
If the spec says which values have the same hash then this opens up code to collisions attacks.
If the spec says it's random which values have the same hash then it's non-deterministic if the program prints foo.

is my understanding of the problem statement with the  various design constraints put forward from committee.
09:14
<eemeli>
Plus, AFAIK equals/hash is how every implementation implements maps natively, it's just not exposed to user code.
Ashley Claymore: I understood from this earlier assertion by Ron that implementations have already found ways to square the circle with respect to "different and never equal", but maybe I misunderstood?
09:18
<nicolo-ribaudo>

Hashes can have conflicts — in a Map you store a list of entries per each hash. When looking up values in the map, you:

  1. Compute the has of the key
  2. Get the list corresponding to that hash
  3. Iterate through the list to check if the key is indeed there
09:26
<Ashley Claymore>
Ashley Claymore: I understood from this earlier assertion by Ron that implementations have already found ways to square the circle with respect to "different and never equal", but maybe I misunderstood?
Yep. The important part is that this isn't exposed. Apart from trying to measure the execution time. Doing `map.set("a", 1)` I can't tell if there was a hash collision and it had to probe, or the buckets needed to be-resized 
09:53
<eemeli>

Ah, got it. So for a "different and never equal" sort of hash, hash() would need to remember all the values that have passed through it.

A related thought I had that could limit the impact of that would be making the hash (or composite key, not really sure how they'd be very different) linked to the lifetime of an object. Then hash(foo, "a") and hash(bar, "a") would never equal if foo !== bar, but hash(obj, { a: 1, b: 2}) === hash(obj, { b: 2, a: 1 }) could work.

12:05
<Ashley Claymore>
That is essentially the CompositeKey / R&T design. That doesn't help Ron's use case of building high performance custom hash maps. To implement custom hash maps the only truely useful hashing function is one where it is possible for values to collide, otherwise the space hasn't been reduced to something that can fit into a small number of indexable buckets
12:07
<Ashley Claymore>
this is why Ron desires a hashing function that returns integers
12:16
<Ashley Claymore>
I would like to think that the object based R&T provide lots of value without needing to expose hashing. While also not precluding another proposal for exposing hashing function for the use cases when full control and minimal object allocations are desirable. For use cases when R&T can be adopted as the data model of the application, this would be efficient. Using these values as Map/Set keys would require not extra allocation (outside of the map/set's own storage naturally), and the hashing and equality functions would be 100% native with zero need for userland re-entrancy guards. However Ron is correct that when R&T need to be created as keys because the data model and the keys are not directly compatible then those application will need to allocate more objects to create the keys. This should be more memory efficient than the current solution of flattening values into one long string, but is still an extra allocation that a purely manual hashing+equality interface would avoid.
12:26
<littledan>
The overhead incurred by a CompositeKey just seems like a non-starter to me. Maybe we could find another approach that can overcome these caveats. For example, a Hasher class instead of a global Object.hash(). A given Hasher could just use a monotonically increasing number for each unique object instance it comes across, and maybe is configurable as to how it handles string hashing (e.g., algorithm, randomness, seed values, etc.). Then your equaler looks like { equals(a, b), hash(obj, hasher) } and you then can write new Map([], { equaler, hasher }). Object hashes could be deterministic without being a global communications channel. String hashing can be deterministic if you want it to be, or not, as best fits your scenario.
In this hasher scenario, I don't really understand why not just use the object as a representative of its own binary hash, and I don't understand what you're proposing for compound values
12:28
<littledan>
it was not clear to me that this feedback applied to interning at construction time, with a constructor function rather than syntax
We didn't present the interning idea at that pivotal TC39 meeting because it had already been ruled out for the reasons Ashley presented about the cost of interning, based on previous conversations with implementers (which we maybe should've repeated in committee)
12:29
<littledan>
shu: Could you clarify whether "better" is good enough, when it comes to interning overhead?
12:30
<rbuckton>
I want to compare object equality structurally, not by reference. The most efficient way to do so for custom equality in a hash table is to calculate a hashcode for bucketing, and use an equals method against each element in the bucket. A user-defined hash table cannot use object reference identity as it is not a numeric value, instead it needs an identity hash.
12:32
<nicolo-ribaudo>
Can you convert an object identity into a number by having a WeakMap<object, number>, and assigning a number to each object?
12:36
<littledan>
let's see if we can avoid these weak things where possible... it has a real GC perf cost
12:37
<littledan>
rbuckton: If your goal is to have a concurrent map, maybe we should focus on that. That could work both by identity (by default) and converting to a R&T for structural comparison (opt in with a keyBy function). Would that implement what you need?
12:37
<rbuckton>

That is what is generally done to work around this currently, but it has drawbacks:

  • It's not resilient to version conflicts when multiple versions of the same package are installed.
  • If globally accessible, it must be randomized to avoid becoming a communications channel.
  • It doesn't work across threads for something like shared structs.

It's also only part of the problem with hashing, the other problem is strings.

12:47
<littledan>
a concurrent map would hash strings well, right?
12:47
<littledan>

That is what is generally done to work around this currently, but it has drawbacks:

  • It's not resilient to version conflicts when multiple versions of the same package are installed.
  • If globally accessible, it must be randomized to avoid becoming a communications channel.
  • It doesn't work across threads for something like shared structs.

It's also only part of the problem with hashing, the other problem is strings.

How can you solve these problems with a custom data structure?
12:48
<littledan>
I'm not sure what you mean by "this is what is done" -- the concurrent map construct doesn't exist yet
12:49
<rbuckton>
rbuckton: If your goal is to have a concurrent map, maybe we should focus on that. That could work both by identity (by default) and converting to a R&T for structural comparison (opt in with a keyBy function). Would that implement what you need?
One of my goals is performance. Composite keys, or using R&T as keys, is going to have overhead. If I want to use a Uri or a Point or a Location as a key, I would have to convert it to a R&T type or composite key first, which is an allocation for every call to get/set/has. This can be 1000s of allocations in a tight loop, and if I don't own Uri or Point or Location I can't just convert those to be R&T types. An { equals, hash } object is a single allocation that is reused for every key.
Another of my goals is maturing the language. Custom collection classes can't perform as well as native Map/Set because developers don't have access to the requisite core capabilities necessary to make that happen. I'm concerned that composite keys either become evolutionary dead end for the language if these building blocks become available, or they become a rationale to never make these building blocks available and thus we never have the flexibility to write efficient custom collections.
12:51
<rbuckton>
I'm not sure what you mean by "this is what is done" -- the concurrent map construct doesn't exist yet
I was responding to nicolo. Also, a concurrent map does exist. I had to build one for TypeScript as part of experimenting with shared structs. The lack of an Object.hash() required a significant number of workarounds that I wouldn't want to rely on in a released product.
12:54
<littledan>
Yes, I can see how composite keys or R&T has more runtime cost than a hashcode. That is sometimes the compromise that we make in high-level languages. I guess when domain-specific hacks are possible, they can be included as the keyBy.
12:54
<littledan>
I was responding to nicolo. Also, a concurrent map does exist. I had to build one for TypeScript as part of experimenting with shared structs. The lack of an Object.hash() required a significant number of workarounds that I wouldn't want to rely on in a released product.
ah, now I understand why you identify strings as an issue
12:54
<rbuckton>
And yes, if concurrent collections were part of the MVP for shared structs then I might be less concerned, but I don't see that being likely.
12:54
<rbuckton>
Strings are an issue for multiple reasons.
12:55
<littledan>
And yes, if concurrent collections were part of the MVP for shared structs then I might be less concerned, but I don't see that being likely.
I can understand your concern there but in this chat we've identified some serious concerns with exposing identity hashcodes... maybe it would work better to push on ConcurrentMap as a follow-on for shared structs, rather than these lower-level utilities in other parts of the language.
12:55
<rbuckton>
  • Implementations need to be able to choose more efficient string hashing algorithms as they become available, and thus requires non-determinism between upgrades.
  • Strings are a notorious source for hash collisions, and thus requires non-determinism between app restarts.
12:56
<littledan>
  • Implementations need to be able to choose more efficient string hashing algorithms as they become available, and thus requires non-determinism between upgrades.
  • Strings are a notorious source for hash collisions, and thus requires non-determinism between app restarts.
these sound like issues if you're going to implement your own string hashtable; if this task is offloaded to the VM, it might be easier
13:01
<rbuckton>
I can understand your concern there but in this chat we've identified some serious concerns with exposing identity hashcodes... maybe it would work better to push on ConcurrentMap as a follow-on for shared structs, rather than these lower-level utilities in other parts of the language.
I need more context as to why this is a serious concern. The argument's I've heard for only advancing deterministic APIs do not seem convincing to me. Non-determinism is good for security. Non-determinism is good for performance. Non-determinism is necessary for a large percentage of existing applications. The only rationale I've heard is replay of execution, but there are other technologies for that and it depends on denial of Math.random() and Date.now() (and Temporal.Now, and numerous other sources of randomness). Object.hash() could just as easily be denied or made deterministic to serve that case. Determinism for the sake of Determinism does not serve the web platform.
13:02
<littledan>
We already know that we must not expose a deterministic identity hashcode operation. But we also have serious issues around nondeterminism. I'm especially concerned with the interop risks over time. This all is why we're currently taking the middle path of hiding the identity hashcode.
13:04
<Ashley Claymore>
userland re-entracy is also a common concern for proposals. If the equals function is written in userland, this puts userland re-entracy right at the heart of the map internal bucket probing loop. I'll leave it to engine implementations to state how much of a concern that is to them.
13:04
<littledan>
what do you mean by reentrancy here?
13:04
<Ashley Claymore>
leaving C++
13:04
<Ashley Claymore>
back to the application's logic
13:05
<Ashley Claymore>
which could re-enter the currently executing function. Invalidating pointers, if this re-entrancy was not taken into account. e.g. the buckets being re-sized while walked
13:05
<rbuckton>
We already know that we must not expose a deterministic identity hashcode operation. But we also have serious issues around nondeterminism. I'm especially concerned with the interop risks over time. This all is why we're currently taking the middle path of hiding the identity hashcode.
Can you expand on your concerns about interop risks? A hashcode is not a portable value. It is only relevant during the life of the application. That is the case in every language as far as I know.
13:06
<littledan>
It's easy to imagine someone depending on string hashcodes having certain properties
13:07
<littledan>
for example you could check whether the hashcode has changed as an indicator of whether your JS program has restarted, which feels off
13:09
<rbuckton>
I can understand your concern there but in this chat we've identified some serious concerns with exposing identity hashcodes... maybe it would work better to push on ConcurrentMap as a follow-on for shared structs, rather than these lower-level utilities in other parts of the language.
Getting shared structs through committee is going to be an uphill battle. Concurrent collections are a necessity to make them practically useful in large-scale applications. If the MVP for shared structs suddenly became stage 4 today I still wouldn't be able to use them for TypeScript without having to build my own concurrent Deque, Work stealing queue, ConcurrentMap, and ConcurrentBag/ConcurrentSet.
13:10
<littledan>
Getting shared structs through committee is going to be an uphill battle. Concurrent collections are a necessity to make them practically useful in large-scale applications. If the MVP for shared structs suddenly became stage 4 today I still wouldn't be able to use them for TypeScript without having to build my own concurrent Deque, Work stealing queue, ConcurrentMap, and ConcurrentBag/ConcurrentSet.
sounds like a good argument that we should have a built-in ConcurrentMap, whether as part of shared structs or a follow-on proposal
13:10
<rbuckton>
for example you could check whether the hashcode has changed as an indicator of whether your JS program has restarted, which feels off
There are easier ways to do this that don't depend on Object.hash()
13:10
<yulia>
shu: I hear you have concerns re: import defer and it's usability for the web. happy to talk about it
13:11
<littledan>
"this is an uphill battle" doesn't necessarily imply that taking the battle somewhere else will make it easier
13:11
<rbuckton>
"this is an uphill battle" doesn't necessarily imply that taking the battle somewhere else will make it easier
There's nowhere else to take it, IMO.
13:12
<rbuckton>
It's easy to imagine someone depending on string hashcodes having certain properties
Pretty much every hash generation API in every language includes documentation that hash codes are not stable across application restarts.
13:23
<rbuckton>
JS performance is a hot button issue in the ecosystem. The "why not rewrite in Rust" crowd is growing louder and louder, especially now that WASM-GC is available, and it's becoming easier and easier for people to move off of JS. I want the JS language to evolve and mature, to continue to be relevant. I don't want to end up with JS just being for hobbyists because the rest of the world has moved on. We mature by adding flexibility, smoothing rough edges, improving performance, improving ergonomics, simplifying common tasks, and adding new capabilities that open the doors for new classes of applications. JS is not a specialized language, it is a general purpose language, and that's its strength.
13:50
<ryzokuken>
good morning/evening/night delegates! meeting starting in ~10m.
13:58
<shu>
shu: I hear you have concerns re: import defer and it's usability for the web. happy to talk about it
will take you up on that
14:03
<shu>
shu: Could you clarify whether "better" is good enough, when it comes to interning overhead?
not at this time
14:04
<Duncan MacGregor>
I would volunteer for note taking, but I'm getting pinged on non-TC39 things a lot right now.
14:05
<ryzokuken>
I would volunteer for note taking, but I'm getting pinged on non-TC39 things a lot right now.
understandable, thanks anyway!
14:23
<Michael Ficarra>
hard agree with USA here
14:24
<littledan>
hard agree with USA here
could you elaborate?
14:26
<Michael Ficarra>
stage 1 acknowledges that localisation is important for the web and something we should try to address within TC39; that remains the case, regardless of how long it will take or how hard it might be to provide a solution
14:30
<littledan>
stage 1 acknowledges that localisation is important for the web and something we should try to address within TC39; that remains the case, regardless of how long it will take or how hard it might be to provide a solution
Where is the point where you're disagreeing with USA?
14:30
<Michael Ficarra>
... nowhere?
14:30
<littledan>
oh oops I misread sorry
14:31
<littledan>
somehow turned into hard disagree
14:39
<Mathieu Hofman>

How would a concurrent map actually be implemented in the context of wasm-gc? Isn't there the same kind of problems that you have an opaque identity for your objects (and possibly strings) like in JS?

I actually still fail to understand what the data model is for your use case, and as such what the solution space might look like. You want structural comparisons for your collection entries, but these values are shared? How can a structural comparison be stable if the data itself can change from under you? Could part of the "solution" be to make R/T sharable and valid values inside shared structs?

IMO, the ability to shim new features is highly preferable, but it rarely needs to be efficient / performant, nor does it need to be ergonomic for the shim implementer. I'd rather have a cohesive language that doesn't expose sharp edges.

14:40
<Ashley Claymore>
+1 to finding ways to reduce champion burnout. There's a reason why it was 2022 when I last presented R&T :)
14:41
<littledan>
it has not reached an impasse
14:50
<nicolo-ribaudo>
I was scrolling through Twitter and found another case of somebody not realizing that using requires a binding even if it's not actually needed: https://twitter.com/nullvoxpopuli/status/1777364717805142411
14:51
<nicolo-ribaudo>
Maybe we could allow using LeftHandSideExpression ;
14:51
<nicolo-ribaudo>
It would beed a cover grammar, but using fn() is much better than using void = fn()
14:52
<eemeli>
I used "impasse", or "stuck" in the issue, to indicate that no actions only within TC39 are sufficient for further progress. The impasse may be resolved by actions outside the committee, i.e. industry feedback and/or adoption. But didn't want to get into an argument about semantics.
14:52
<nicolo-ribaudo>
It would beed a cover grammar, but using fn() is much better than using void = fn()
And then using void = would be a syntax error like let void = and const void =
14:53
<Michael Ficarra>
@nicolo-ribaudo https://github.com/tc39/proposal-discard-binding/issues/1#issuecomment-2030365690
14:55
<nicolo-ribaudo>
Thank you
14:59
<hax (HE Shi-Jun)>
I really like allow redeclare _ ...
15:05
<Ashley Claymore>
https://johnnyreilly.com/typescript-eslint-no-unused-vars
15:06
<Ashley Claymore>
^^ for reference to what RBN said about eslint config
15:07
<Duncan MacGregor>

I think the Risks and Assumptions section in the Java JEP for unnamed variables is interesting:

We assume that little if any actively-maintained code uses underscore as a variable name. Developers migrating from Java 7 to Java 22 without having seen the warnings issued in Java 8 or the errors issued since Java 9 could be surprised. They face the risk of dealing with compile-time errors when reading or writing variables named _ and when declaring any other kind of element (class, field, etc.) with the name _.

15:07
<dminor>
This just feels risky to me, maybe it's better to keep this in extractors and pattern matching and explicit resource management, even if it would be nice to have it elsewhere.
15:07
<ljharb>
fwiw eslint supports an ignore prefix pattern for the reason ron just mentioned
15:08
<littledan>
This just feels risky to me, maybe it's better to keep this in extractors and pattern matching and explicit resource management, even if it would be nice to have it elsewhere.
it seems kinda weird to me to restrict this construct to just pattern matching and explicit resource management, but if we can't have it elsewhere, that's maybe OK
15:09
<dminor>
I agree it would be weird
15:09
<dminor>
But there's no risk of breaking existing code, and we can have _ there, right?
15:10
<shu>
i don't understand how any existing variable name can be web compat
15:11
<dminor>
It would be nice to use _ in pattern matching, given precedent in other languages with pattern matching
15:11
<rbuckton>
const _ = a, _ = b is illegal everywhere
15:11
<littledan>
i don't understand how any existing variable name can be web compat
this is if it's restricted to the new constructs
15:11
<nicolo-ribaudo>
i don't understand how any existing variable name can be web compat
Instead of "redeclaring a variable is an error in strict mode" it becomes "reading a variable that has been redeclared in strict mode is an error"
15:12
<nicolo-ribaudo>
Instead of "redeclaring a variable is an error in strict mode" it becomes "reading a variable that has been redeclared in strict mode is an error"
"strict mode" -> I mean let, const, and strict params
15:12
<ljharb>
but var _ = 1, _ = 2; is not illegal anywhere
15:12
<nicolo-ribaudo>
but var _ = 1, _ = 2; is not illegal anywhere
And nobody is proposing changing how it works
15:12
<Jack Works>
I don't like _ as discard. What's wrong with void? Is there any pushback on void?
15:13
<ljharb>
what about function f(_, _) {}, which is legal in sloppy mode?
15:13
<shu>
Instead of "redeclaring a variable is an error in strict mode" it becomes "reading a variable that has been redeclared in strict mode is an error"
sloppy mode so popular though?
15:13
<eemeli>
To me "void" sounds an awful lot like "undefined".
15:13
<ryzokuken>
for C/C++ developers it could be a source of confusion, yes
15:13
<ljharb>
and indeed the binding is not defined :-p
15:14
<nicolo-ribaudo>
sloppy mode so popular though?
The error cases remain the same, with the difference that the error is moved from declaration position to reference position
15:14
<nicolo-ribaudo>
So what works in any mode would keep working the same
15:14
<Ashley Claymore>
I assume other languages could cope with _ easier because they didn't have a popular library that was commonly imported as _
15:15
<shu>
The error cases remain the same, with the difference that the error is moved from declaration position to reference position
i am still confused what the proposed semantics is for sloppy mode
15:15
<Justin Ridgewell>
@rbuckton Why do we lose assignment patterns with _?
15:15
<ryzokuken>
it's on us for not doing modules/namespaces well at start I guess
15:15
<Justin Ridgewell>
The simple case is just to allow redeclaring _, no other restrictions.
15:15
<nicolo-ribaudo>
i am still confused what the proposed semantics is for sloppy mode
vars and function params can be redeclared, but if you redeclare a let/const then it throws when you reference the binding
15:16
<Ashley Claymore>
it's on us for not doing modules/namespaces well at start I guess
How would that change things?
People do import _ from "lodash"
15:16
<bakkot>
I am totally fine restricting this feature to strict mode
15:16
<Justin Ridgewell>
How would that change things?
People do import _ from "lodash"
Renaming a static import is trivial.
15:16
<Ashley Claymore>
Sure, but popular?
15:17
<ryzokuken>
plus the _ name is less necessary with imports
15:17
<ryzokuken>
but if all you can do is pollute the global namespace, $ and _ abound.
15:17
<Justin Ridgewell>
They should be using import { foo, bar } from ‘lodash’ anyways…
15:17
<ljharb>
they should be using import foo from 'lodash.foo' anyways, to avoid the CVE noise :-p
15:18
<bakkot>
nothing wrong with namespace imports
15:18
<shu>
vars and function params can be redeclared, but if you redeclare a let/const then it throws when you reference the binding
okay well, i don't think this proposal meets the bar thus far for complicated binding handling
15:18
<ljharb>
nothing wrong with namespace imports
in this case i'm complaining about big-bag-o-things packages, not namespace imports
15:18
<Justin Ridgewell>
nothing wrong with namespace imports
Too many people mistake it for a mutable object, or use it dynamically in a way the minifier can’t eliminate everything else.
15:19
<bakkot>
well don't do that
15:19
<ljharb>
^ the latter is also a problem with barrel files
15:19
<bakkot>
but put the blame where the problem is, not on something else
15:20
<ljharb>
the-world-if-people-didnt-do-things-they-shouldnt-do.jpg
15:21
<danielrosenwasser>
you should see the ~bananas stuff~ similar stuff I've seen in Python
15:22
<Ashley Claymore>
yes please! I have done very little Python and am all ears!
15:22
<danielrosenwasser>
https://docs.djangoproject.com/en/5.0/topics/i18n/translation/
15:23
<danielrosenwasser>
Okay, it's not really bananas - but they're doing the same thing
15:23
<Justin Ridgewell>
okay well, i don't think this proposal meets the bar thus far for complicated binding handling
We don’t need the “reference the binding” restriction.
15:23
<shu>
that would be more palatable
15:24
<ptomato>
aliasing gettext() to _() is a fairly common pattern for programming languages that have an implementation of gettext
15:25
<eemeli>
I'm really interested in extractors effectively providing runtime types: https://github.com/tc39/proposal-extractors/issues/20
15:25
<ptomato>
because the xgettext tool extracts strings inside _() out of source files into message files
15:25
<danielrosenwasser>
Justin Ridgewell: you mean bindings to _ work in a last-write-wins manner and so there's no ambiguity error?
15:25
<Justin Ridgewell>
Yes
15:25
<Justin Ridgewell>
It’s just a regular binding, but it can be redeclared.
15:26
<bakkot>
I like rust's thing where a redeclaration introduces a new scope which shadows the previous one
15:26
<bakkot>
so closures which capture the old one still work
15:26
<bakkot>
at least I assume that's how it works, I haven't actually checked
15:26
<bakkot>
now that I'm saying this I have no idea why I believe it
15:26
<eemeli>

With extractors, you can have

function foo(String(s), Number(n)) { ... }

and be sure of s being a string and n being a number.

15:27
<Ashley Claymore>
I think that's right
15:27
<ljharb>
i think you'd need String(const s) etc to make the binding tho?
15:27
<eemeli>
Nope.
15:27
<Justin Ridgewell>
so closures which capture the old one still work
The closure would have to move the value in. I’m not sure it works the way you think.
15:29
<Michael Ficarra>
@eemeli what Symbol.customMatcher implementation are you thinking of for the built-in constructors?
15:30
<ljharb>
those are already in the pattern matching proposal, fwiw, with brand checking semantics
15:30
<bakkot>
... brand checking or typeof?
15:30
<eemeli>

Something like

class Number {
  ...
  [Symbol.customMatcher](subject) {
    if (typeof subject === 'number') return [subject];
    if (subject instanceof Number) return [Number(subject)];
    return false;
  }
}
15:31
<bakkot>
I am categorically opposed to accepting boxed primitives here or anywhere
15:31
<ljharb>
it'd check for the internal slot of a Number instead of instanceof, but otherwise yes
15:31
<Justin Ridgewell>
The closure would have to move the value in. I’m not sure it works the way you think.
Actually you can’t reference _ in a closure (I’ve never tried before): https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=ab28412522dba608f6964c9a11ce600f
15:32
<ljharb>
it doesn't make any sense to reject boxed primitives if people are going to be referencing the constructor
15:32
<ljharb>
i don't expect it to come up in practice either way tho, tbf
15:32
<bakkot>
sure it does, Number(foo) doesn't produce a boxed primitive
15:32
<eemeli>
I don't care about boxed primitives tbh.
15:32
<ljharb>
it's a constructor. new Number(foo) does
15:33
<bakkot>
if the extractor used new I would agree with you
15:33
<bakkot>
but it doesn't
15:33
<Michael Ficarra>
const new Number(n) = 5
15:33
<ljharb>
so you want the requirement to be new UserClass as a pattern to match a user class?
15:33
<eemeli>
new Number is not a valid ExtractorMemberExpression.
15:34
<Michael Ficarra>
@eemeli sorry I forgot the Kappa
15:34
<ljharb>
iow, a pattern of Number and a pattern of UserClass should work the same - either both require new or neither does
15:34
<Michael Ficarra>
I'll go back to TDZ
15:34
<ljharb>
and i don't see any argument for requiring new on non-primitive constructors
15:34
<bakkot>
UserClass(foo) should throw or either behave the same as new UserClass(foo), so it's fine for let UserClass(foo) = bar to be the extractor pattern
15:35
<bakkot>
but Number doesn't work like that, so we can't use that as precedent for how Number should work
15:35
<ljharb>
not if it's an ES5 class
15:35
<ljharb>
and many predicates are technically constructible (because they're not written as arrows or concise methods etc)
15:36
<bakkot>
I don't think "this is technically constructible" ought to guide our design of these features
15:36
<bakkot>
this applies to user-defined predicates and also to Number
15:36
<ljharb>
ok. but we don't actually have a clear thing in JS that divides constructors and functions, so PascalCase is the universal convention that JS devs use for that
15:37
<ljharb>
so, designing for that universal expectation, Number would (and does) look like a constructor just like UserClass or Set
15:37
<bakkot>
users should not regard Number as being constructible and we should not design the feature to suggest that it is
15:37
<bakkot>
it is true that Number is technically constructible but I don't care about this fact, and we should not promote it to people's attention, nor make features which support it
15:38
<ljharb>
i do not believe that it accepting boxed primitives - and unboxing them, ftr - is going to encourage usage of it. i strongly believe the opposite
15:38
<Justin Ridgewell>
Actually you can’t reference _ in a closure (I’ve never tried before): https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=ab28412522dba608f6964c9a11ce600f
Anyways, the value is moved into the closure when you create it, so it’s not redeclaring a scope: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=0be61d281692f23d745632011a77a86b
15:38
<bakkot>
i do not believe that it accepting boxed primitives - and unboxing them, ftr - is going to encourage usage of it. i strongly believe the opposite
... say more?
15:38
<ljharb>
certainly no new sites of producing a boxed primitive should ever be added
15:39
<ljharb>
if you want to use pattern matching, with the current semantics, even if you do the very unlikely thing of making a boxed primitive, you will as rapidly as possible unbox it. just like how sparse arrays are terribad, and all new stuff just papers over that by making them dense, and we do NOT throw when you have a sparse array, we shouldn't throw or fail when you have a boxed primitive. we should just paper over it and get you into "normal primitives" as fast as possible.
15:39
<littledan>
I thought V8 optimized array destructuring
15:40
<bakkot>
if you want to use pattern matching, with the current semantics, even if you do the very unlikely thing of making a boxed primitive, you will as rapidly as possible unbox it.

just like how sparse arrays are terribad, and all new stuff just papers over that by making them dense, and we do NOT throw when you have a sparse array, we shouldn't throw or fail when you have a boxed primitive. we should just paper over it and get you into "normal primitives" as fast as possible.
we don't throw when we have a sparse array because it would require a special case to do so
15:40
<hax (HE Shi-Jun)>
I thought V8 optimized array destructuring
I remember they published an article about it several years ago
15:40
<bakkot>
but here you're suggesting we add a special case to support the thing that people should not encounter
15:40
<bakkot>
I want to not do that
15:40
<littledan>
we have to avoid saying "stage n concern" because of the ambiguity between this being a thing to resolve before reaching stage n, or only once stage n is reached
15:41
<bakkot>
if user is passing around a boxed primitive, they should expect to get an error, not for the language to accommodate them
15:41
<ljharb>
p sure the motivation was more "because that hurts people to no benefit" as opposed to "we don't want a special case in the spec/impl"
15:43
<bakkot>
I do not think that's right, but in any case, boxed primitives are more different from primitives than holey arrays are from dense arrays
15:45
<Justin Ridgewell>
I thought V8 optimized array destructuring
It’s still not nearly as fast as regular property destrutcuring
15:45
<ljharb>
boxed primitives exist. if they are used, then providing encouragement and ergonomics for quickly getting them unboxed is good. if they are not used, then accepting them provides no such encouragement.
15:45
<Ashley Claymore>
I remember they published an article about it several years ago
https://docs.google.com/document/d/1hWb-lQW4NSG9yRpyyiAA_9Ktytd5lypLnVLhPX9vamE/edit#heading=h.9ss45aibqpw2
15:46
<bakkot>
counterpoint: if they are used, discouraging their use is good
15:46
<bakkot>
no code should accept them
15:46
<bakkot>
(unless it accepts other objects, of course)
15:50
<bakkot>
I am not looking forward to parsing (bar(baz)) => 0
15:50
<littledan>
extractors do a good job addressing one of the points that Yulia raised a while ago, about the availability of parallel constructs inside and outside of pattern matching.
15:50
<bakkot>
but I guess it's not that much worse than ({ bar }) => 0
15:51
<bakkot>
and async(bar(baz)) => 0 of course
15:52
<littledan>
yeah by 2017 we already got up to a certain bar of annoying-to-parse, and my feeling was that extractors didn't really add anything that was so much more bad... but non-backtracking parsers will have some more complexity in their error tracking with this
15:53
<bakkot>
it is not that much worse, it's just adding more complexity to what is already the single most complex-to-parse construct in the language
15:54
<Michael Ficarra>
the extractor has to be an identifier or a member access on an extractor, right?
15:55
<bakkot>
yeah
15:55
<eemeli>
It can also be e.g. super.
15:55
<bakkot>
https://tc39.es/proposal-extractors/#prod-ExtractorMemberExpression
15:55
<eemeli>
Ah, not super, just super.foo or this.
15:58
<shu>
i mean in general i agree with george but i got bad news for him on the understandability of JS wrt user code today
16:01
<Ashley Claymore>
That's because it's all minified on the web
16:04
<rbuckton>
@rbuckton Why do we lose assignment patterns with _?
Because you can already write [_, _] = ar today and it has a meaning.
16:04
<keith_miller>
Do other engines think that throwing for each failed match seems like it would be way too slow?
16:04
<littledan>
Do other engines think that throwing for each failed match seems like it would be way too slow?
I think the concern was more around the iteration protocol
16:04
<keith_miller>
Or did I just misunderstand the proposal?
16:04
<littledan>
also engines don't have to actually do the throw
16:05
<Justin Ridgewell>
Because you can already write [_, _] = ar today and it has a meaning.
But that’s behaving exactly the way discard bindings should.
16:05
<keith_miller>
You'd need to inline every pattern, IIUC, which doesn't seem plausible in all places (it could be virtual, it could be huge, etc)
16:05
<rbuckton>
Do other engines think that throwing for each failed match seems like it would be way too slow?
While that's more of a discussion for pattern matching, Refutability is a very important mechanic for pattern matching. If you want irrefutability, you use a default: clause.
16:06
<Justin Ridgewell>
We just need the ability to redeclare.
16:07
<keith_miller>
I agree it's more of a discussion for pattern matching I don't want to get to a place where we have throwing here and have to have a totally different mechanism for pattern matching
16:07
<ljharb>
Do other engines think that throwing for each failed match seems like it would be way too slow?
it would be for a failed match not for a failed clause, to be clear - the intention for a match construct is that it always matches something
16:07
<Ashley Claymore>
We just need the ability to redeclare.
which definition would closures capture?
16:07
<rbuckton>
But that’s behaving exactly the way discard bindings should.
Redeclare what? if _ isn't already declared, [_, _] = ar will either throw in strict mode, or introduce a global in non-strict mode. I don't think we can change that. If you want to use _ as a discard in assignments, you do that by declaring a var _ somewhere in the same scope.
16:08
<rbuckton>
Or are you talking about binding patterns?
16:08
<gkz>
i mean in general i agree with george but i got bad news for him on the understandability of JS wrt user code today
If we're creating a brand new feature, why not aim higher? Especially if the increased power/flexibility is not required for the majority of use-cases
16:09
<rbuckton>
I agree it's more of a discussion for pattern matching I don't want to get to a place where we have throwing here and have to have a totally different mechanism for pattern matching
This must throw. If const {} = null throws or const [] = 1 throws, so must this.
16:09
<rbuckton>
match must throw if it has no default. Since match is syntactic, if a default: clause doesn't exist you just synthetically introduce one that throws.
16:10
<shu>
If we're creating a brand new feature, why not aim higher? Especially if the increased power/flexibility is not required for the majority of use-cases
you're reading the wrong conclusion. i, as an engine person, would be very happy with no custom matchers
16:10
<shu>
i'm saying that doesn't meaningfully move the needle for the general problem of understanding user code in JS
16:11
<littledan>
shu: dminor : What was the performance concern?
16:11
<shu>
the new one?
16:12
<shu>
my new one is cover grammars incur cost, as you well know
16:12
<shu>
and we don't like it
16:12
<shu>
nothing deeper than that
16:13
<Justin Ridgewell>
Redeclare what? if _ isn't already declared, [_, _] = ar will either throw in strict mode, or introduce a global in non-strict mode. I don't think we can change that. If you want to use _ as a discard in assignments, you do that by declaring a var _ somewhere in the same scope.
Oh, I was assuming _ was already a binding in scope. Why can’t we make sloppy mode continue to function as is, and just not create the binding in strict?
16:13
<rbuckton>

Regarding the const InventoryID(customerId) = "Contoso-12345" example, I've regularly described extractors as unapplication (the Scala method is literally called unapply()). This dualilty is important for both data types and primitive types:

const InventoryID(customerID, Name) = InventoryID("12345", "Contoso");
16:15
<rbuckton>
Oh, I was assuming _ was already a binding in scope. Why can’t we make sloppy mode continue to function as is, and just not create the binding in strict?

That would definitely be a violation of ljharb 's concerns. I think we can only use _ if it would otherwise be syntactically illegal. If you have

var _ = require("lodash");
function f() {
  [_, _] = [1, 2, 3];
}

Then you're breaking code non-locally.

16:16
<Justin Ridgewell>

That would definitely be a violation of ljharb 's concerns. I think we can only use _ if it would otherwise be syntactically illegal. If you have

var _ = require("lodash");
function f() {
  [_, _] = [1, 2, 3];
}

Then you're breaking code non-locally.

We had that discussion above. Renaming the lodash import is trivial.
16:17
<rbuckton>
In a 10,000 line file, it's easy to lose sight of the imports at the top of the file.
16:18
<ljharb>
oof, if you have a 10k line file you have bigger problems than a binding no longer working
16:19
<gkz>

Regarding the const InventoryID(customerId) = "Contoso-12345" example, I've regularly described extractors as unapplication (the Scala method is literally called unapply()). This dualilty is important for both data types and primitive types:

const InventoryID(customerID, Name) = InventoryID("12345", "Contoso");
I don't disagree that there are use-cases that would be addressed with this, just that the power required to support them (when these are the minority of use-cases, and other languages e.g. OCaml do fine without supporting this), allows completely arbitrary behavior which means that it will be more difficult to understand and analyze the main use-case, which is matching against some datatype and extracting values from it.
16:19
<rbuckton>
Yeah, yeah. I'd love to refactor checker.ts to something more manageable, but that's still a ways out.
16:20
<rbuckton>
I don't disagree that there are use-cases that would be addressed with this, just that the power required to support them (when these are the minority of use-cases, and other languages e.g. OCaml do fine without supporting this), allows completely arbitrary behavior which means that it will be more difficult to understand and analyze the main use-case, which is matching against some datatype and extracting values from it.
Do you mean something more like Python's __match_args__ approach? I've discussed that with the pattern matching champions and there was fairly strong opposition.
16:21
<eemeli>
rbuckton: Are there any dynamically typed languages that support extractors?
16:23
<rbuckton>
OCaml is a statically typed language, as is Rust. They have type systems to do the work for them. JS does not. It often depends on runtime evaluation of code to do similar things. Extractors is the way it is because it isn't statically typed.
16:23
<eemeli>
Racket, apparently?
16:24
<gkz>
Do you mean something more like Python's __match_args__ approach? I've discussed that with the pattern matching champions and there was fairly strong opposition.
I'm not familiar with that feature, but to explain what I mean in loose terms, you could still have a customMatcher-like function which can decide which values to return for extraction, but the check of "is this of this datatype" (be it instanceof or whatever) would always be run, separating these two concerns
16:24
<rbuckton>
rbuckton: Are there any dynamically typed languages that support extractors?
Python has __match_args__, not full extractors. They'd considered them but determined they didn't have use cases. JS has private state, which Python does not, so a __match_args__ approach is not sufficient. Other Pattern Matching champions have opinions on this as well.
16:24
<gkz>
Dart recently added a pattern matching feature, and seems to work in this way: https://dart.dev/language/pattern-types#object
16:27
<rbuckton>
I'm not familiar with that feature, but to explain what I mean in loose terms, you could still have a customMatcher-like function which can decide which values to return for extraction, but the check of "is this of this datatype" (be it instanceof or whatever) would always be run, separating these two concerns

In Python, you can do

match p:
  case Point(x=0, y=0): ...

where Point.__match_args__ is ["x", "y"].

It checks if p is a Point, then basically checks if p[Point.__match_args__[0]] == 0 and p[Point.__match_args__[1]] == 0

16:29
<rbuckton>
There is no opportunity to run user-defined code as part of that validation. It can also only read public properties, but that's fine for Python since it does not have privacy. A Point[Symbol.matchArgs] = ["#x", "#y"] in JS wouldn't be able to read a private #x in JS.
16:32
<littledan>
Is Mark Miller here this afternoon?
16:32
<littledan>
my presentation is largely about refuting a past argument that he made
16:32
<eemeli>
Random thought: Would @@customMatcher as a generator be potentially more performant than returning an array or iterator?
16:35
<littledan>
my presentation is largely about refuting a past argument that he made
he seems to be in the call, so I'll go ahead
16:36
<littledan>
Random thought: Would @@customMatcher as a generator be potentially more performant than returning an array or iterator?
what does this question mean? a generator returns an iterator.
16:36
<littledan>
you can intercept __getattr__ though!
16:39
<eemeli>

littledan: I mean having

class Number {
  ...
  *[Symbol.customMatcher](subject) {
    if (typeof subject === 'number') yield subject;
    else if (subject instanceof Number) yield Number(subject);
    else return false;
    return true;
  }
}

instead of

class Number {
  ...
  [Symbol.customMatcher](subject) {
    if (typeof subject === 'number') return [subject];
    if (subject instanceof Number) return [Number(subject)];
    return false;
  }
}
16:39
<mgaudet>
(side note: Can someone update the topic to point to this meeting rather than feb)
16:41
<gkz>
There is no opportunity to run user-defined code as part of that validation. It can also only read public properties, but that's fine for Python since it does not have privacy. A Point[Symbol.matchArgs] = ["#x", "#y"] in JS wouldn't be able to read a private #x in JS.

I like this aspect of the feature:
"Whether a match succeeds or not is determined by the equivalent of an isinstance call. If the subject (shape, in the example) is not an instance of the named class (Point or Rectangle), the match fails."

But my focus is on that, if you wanted to still have a Symbol.matchArgs function to determine the values being extracted, which can access private properties, that could be fine.
This simply separates the concerns of "is this a value of this datatype" and "what values should be extracted" rather than combining them into one function and allowing both to be user-defined.

16:43
<rbuckton>
That does not satisfy my goals and motivations. Pattern matching could do that with x is Point and { let x, let y }. Executing user-defined code is the whole point of the feature.
16:45
<Mathieu Hofman>
Is Mark Miller here this afternoon?
he should be, I'll make sure he is
16:45
<gkz>
To clarify, with that example it would be x is Point(let x, let y) - we check if x instanceof Point, and then get the arguments you want extracted
16:45
<gkz>
If would just not allow things like using extractors on strings to extract parts of those strings
16:46
<gkz>
I guess this would be similar to how both Python and Dart use these features
16:47
<rbuckton>
I want to be able to do that with this feature. Restricting it to data types is a manufactured limitation, not a natural limitation of the syntax.
16:47
<Anthony Bullard>
I guess this would be similar to how both Python and Dart use these features
It is very similar to Dart. Which has been well received
16:47
<rbuckton>
Also, instanceof is broken. Pattern matching is not using it.
16:48
<gkz>
Sure, whatever actual check is more appropriate to use, I think the general idea I'm talking about is clear though
16:49
<iain>
Random thought: Would @@customMatcher as a generator be potentially more performant than returning an array or iterator?
No. Returning an array is the least-bad case for the iterator protocol.
16:49
<rbuckton>
I understand the semantics you are describing. I just see no reason to introduce an artificial limitation.
16:50
<rbuckton>
If a custom matcher is an iterator, then it is irrefutable. It always matches.
16:51
<eemeli>
iain: Presumably with the exception of only returning one value, and handling its destruction as a separate concern.
16:51
<rbuckton>
The reason I pursued this feature in the first place was to support user-defined validation and transformation. That it works for datatypes is a happy benefit.
16:53
<iain>
eemeli: Yes, returning a single value would be significantly more performant
16:53
<rbuckton>
Also, you can destructure a string, so restricting extractors to datatypes breaks that. const { length } = "abc" and const [a, b, c]="abc" are perfectly legal. I don't want extractors to be less capable than {} and [].
16:54
<nicolo-ribaudo>
Webex is again refusing to pick up my audio, hopefully I can fix it on time
16:54
<eemeli>
eemeli: Yes, returning a single value would be significantly more performant
And then you'd need to write e.g. let Point([x, y]) = ... to get the same effect, if you need more than one value out of the extractor.
16:55
<gkz>
I understand the semantics you are describing. I just see no reason to introduce an artificial limitation.
It seems like languages like Dart, Python, OCaml, Rust all have this limitation. And the reason is what I described before, there is a trade-off between the flexibility this feature provides, and making code easy to understand (for both humans and tooling)
16:55
<rbuckton>
Besides, writing an extractor to handle a non-datatype input is more complex than the default implementation for a data type. In the pattern matching proposal, class C {} will have a default [Symbol.customMatcher] that performs a brand check and returns the subject. You get it for free unless you want to do the more complex things.
16:57
<rbuckton>

And ideally, ADT enums would have a default implementation that matched their definition, i.e.:

enum Message {
  Write(text),
  Move({ x, y })
}

const Message.Write(text) = msg1;
const Message.Move({ x, y }) = msg2;

You would have to manually write a [Symbol.customMatcher] method to override this behavior, so it is an opt-in mechanism.

16:58
<rbuckton>
It seems like languages like Dart, Python, OCaml, Rust all have this limitation. And the reason is what I described before, there is a trade-off between the flexibility this feature provides, and making code easy to understand (for both humans and tooling)
C# and Scala do not have this limitation. Scala has unapply, C# has Deconstruct.
16:59
<gkz>
Yeah, given that there are trade-offs and different languages have made different design choices regarding this trade-off, it seems worth discussing at least!
17:00
<littledan>
Slides for my Array.isTemplateObject presentation: https://docs.google.com/presentation/d/1LTlzpboYwKxRwigATcFYEh06CIbZvOvmFdPzkNn7vJI/edit#slide=id.g2cae2397581_0_66
17:01
<rbuckton>
Yeah, given that there are trade-offs and different languages have made different design choices regarding this trade-off, it seems worth discussing at least!
A good place to continue this discussion would be in the the #tc39-pattern-matching:matrix.org room
17:02
<gkz>
Thanks, will join now!
17:08
<eemeli>
Besides, writing an extractor to handle a non-datatype input is more complex than the default implementation for a data type. In the pattern matching proposal, class C {} will have a default [Symbol.customMatcher] that performs a brand check and returns the subject. You get it for free unless you want to do the more complex things.
Is there any plan to move/add those default matchers to extractors?
17:11
<rbuckton>
The exact semantics need to match between destructuring and pattern matching, so yes, most likely. Currently it's being managed as a cross-cutting concern between the two proposals. If extractors in destructuring gets Stage 2, but pattern matching is held back, then we would likely need to move over the default semantics.
17:18
<ljharb>
i don't think stage 2 is the time to separate them, it'd be if extractors is ready for 2.7, and if pattern matching isn't stage 2 and also is content with the design constraints that advancing extractors would cause
17:30
<ljharb>
ok so why is this stage 3 instead of a normative PR? the discussion was hard to follow
17:31
<Michael Ficarra>
it should return a spec enum or a string
17:31
<littledan>
ok so why is this stage 3 instead of a normative PR? the discussion was hard to follow
I don't think we really discussed that, but is there a problem with considering it Stage 3?
17:31
<littledan>
we can still reconsider
17:31
<ljharb>
just more ceremony than we probably need
17:31
<littledan>
IMO it'd be valid either way
17:33
<ptomato>
+1 would be good to document where the dividing line is. I agree with Jordan in that I would've preferred this to be a needs-consensus PR
17:33
<littledan>
Nicolo's answer seemed good: the stage process let us develop this proposal and build consensus incrementally
17:34
<ljharb>
was this one even a proposal before today?
17:34
<nicolo-ribaudo>
was this one even a proposal before today?
Yes, "dynamic code brand checks", stage 1
17:34
<ljharb>
aha, thanks
17:35
<ljharb>
i'm not sure being stage 1 for many years, and suddenly getting revived and jumping straight to stage 3, justifies it being a proposal vs a PR, but it really doesn't matter much :-)
17:36
<nicolo-ribaudo>
To be honest I spent the last two weeks swinging between "stage 3" and "normative PR", but given that we are changing behavior of eval() (and dropping the guarantee that all objects are returned as-is), I preferred being more conservative
17:36
<nicolo-ribaudo>
Implementations are in progress, so this will be ready for stage 4 soon
17:47
<Michael Ficarra>
🤔 could you data URI an iframe and then pull the "literal" template out of it?
17:48
<Michael Ficarra>
@bakkot ^
17:48
<bakkot>
no, those are cross-origin
17:48
<Michael Ficarra>
okay good
17:49
<Michael Ficarra>
I can never remember how data URIs and file/loopback URIs can interact with web pages
17:49
<bakkot>
data URIs are opaque (=unique) origins in all contexts I am pretty sure
17:50
<Michael Ficarra>
that's good, and I'm sure I will forget before the next time I need that piece of information
18:00
<bakkot>
I have to drop for a bit but I'm happy with this proposal with either cross-realm or same-realm semantics
18:09
<Michael Ficarra>
is this only useful for bundlers? if so, can't they start using a non-standard directive?
18:13
<littledan>
I kinda share danielrosenwasser 's composability concern
18:13
<hax (HE Shi-Jun)>
nicolo-ribaudo: I believe "adding TLA currently considered to be a semver-major change" should be true...
18:14
<shu>
is this only useful for bundlers? if so, can't they start using a non-standard directive?
i'd appreciate your putting this on queue
18:14
<ljharb>
adding TLA is always a breaking change, with or without this proposal
18:15
<shu>
if other people share daniel r's view that this is a dev-time thing, that seems to segue naturally into tools doing better here
18:15
<hax (HE Shi-Jun)>
is this only useful for bundlers? if so, can't they start using a non-standard directive?
It's also useful if someone want to make it clear some code is rely on sync semantic.
18:15
<Michael Ficarra>
feel free to take it from me @shu
18:15
<Michael Ficarra>
I do feel like it's a dev time thing though
18:16
<keith_miller>
Yeah feels like a dev time issue
18:16
<ljharb>
imo we shouldn't have allowed static import of a module using TLA in the first place
18:17
<littledan>
Does this proposal have a spec?
18:17
<Michael Ficarra>
I find the "you'll get an error anyway" argument compelling
18:17
<hax (HE Shi-Jun)>
I kinda share danielrosenwasser 's composability concern
The root cause is TLA is already lose composability ...
18:19
<hax (HE Shi-Jun)>
To be honest, I really feel this proposal is just a patch for TLA footguns...
18:19
<littledan>
re semver: What if we standardized semver in Ecma? Darcy Clarke is interested in this, with a better specification than exists currently (including Node semver ecosystem reality). We could do it in a new TC in Ecma, or maybe a TC39 TG. Who's in? Eventually maybe it'd make sense for JavaScript to have a small built-in library for working with semver values, once we have the core defined (this is an extremely popular npm package).
18:20
<Ashley Claymore>
I wonder if the service worker API could be updated to not have the late-listener issue
18:20
<nicolo-ribaudo>
re semver: What if we standardized semver in Ecma? Darcy Clarke is interested in this, with a better specification than exists currently (including Node semver ecosystem reality). We could do it in a new TC in Ecma, or maybe a TC39 TG. Who's in? Eventually maybe it'd make sense for JavaScript to have a small built-in library for working with semver values, once we have the core defined (this is an extremely popular npm package).
I spent enough time in the past month fighting with semver implementations that don't follow the community spec that I'd love to do it
18:22
<hax (HE Shi-Jun)>
imo we shouldn't have allowed static import of a module using TLA in the first place
At least not by default (it should only be allowed with <script async type=module> )
18:25
<ryzokuken>
re semver: What if we standardized semver in Ecma? Darcy Clarke is interested in this, with a better specification than exists currently (including Node semver ecosystem reality). We could do it in a new TC in Ecma, or maybe a TC39 TG. Who's in? Eventually maybe it'd make sense for JavaScript to have a small built-in library for working with semver values, once we have the core defined (this is an extremely popular npm package).
more standardization opportunities are never bad! it'll help ECMA diversify/gain more popularity and it'll allow us to be more involved in semver... win-win!
18:26
<dminor>
Would this be useful in conjunction with deferred imports to catch the switch to eager loading?
18:26
<ljharb>
there's two open PRs on the semver spec that, once landed, would make semver v3 actually match npm (neither v1 nor v2 is what npm does, and what npm does is what matters) - and that's what i'd hope lands in the language
18:27
<ljharb>
(i'm already working with semver folks to try to get one of those landed)
18:27
<nicolo-ribaudo>

Maybe we could have it only for deferred imports rather than as a general language feature

import defer * as ns from "x" with { async: "error" }
import defer * as ns from "x" with { async: "eager" }
18:29
<Ashley Claymore>

Can this be tested like:

import "./start-test.js";
import "./app.js";
import { wasThereATick } from "./end-test.js"; // shares state with start-test
assert(!wasThereATick);
18:29
<nicolo-ribaudo>

Can this be tested like:

import "./start-test.js";
import "./app.js";
import { wasThereATick } from "./end-test.js"; // shares state with start-test
assert(!wasThereATick);
./end-test.js doesn't wait for ./app.js to be done
18:29
<nicolo-ribaudo>
You would need to inline ./end-test in your module
18:30
<Ashley Claymore>
ah right
18:30
<Ashley Claymore>
wrap them up
18:30
<Ashley Claymore>

so like:

// end-test-wrapper.js
import "./app.js";
import { wasThereATick } from "./end-test.js"; // shares state with start-test
18:30
<nicolo-ribaudo>
well, again ./end-test.js doesn't wait for ./app.js 😛
18:31
<littledan>
+100 to Jordan, [Symbol.toStringTag] was just a mistake
18:31
<Ashley Claymore>

how about

import { ticked } "./start-test.js";
import "./app.js";
assert(!ticked); // ticked live binding
18:32
<Michael Ficarra>
tbf brand checking is icky and I don't like it
18:32
<nicolo-ribaudo>
import { ticksCount } from "./dynamic-ticks-counter";
import "./app.js";
assert(ticksCount === 0)
18:35
<Ashley Claymore>

Yeah this seems to work in Node

// start-test.js
export let ticked = false;
Promise.resolve().then(() => {
    ticked = true;
});
18:35
<Ashley Claymore>
If I add a TLA to "app.js" the assert fails
18:37
<Jack Works>

how about

import { ticked } "./start-test.js";
import "./app.js";
assert(!ticked); // ticked live binding
this is like implementing lodash.throttle but you also need to be a ES module specialist
18:38
<hax (HE Shi-Jun)>
Is Error.isError(new Proxy(new Error, {})) returns true?
18:39
<ljharb>
in the 9 year old spec, yes. it's fine if it returns false tho
18:39
<Ashley Claymore>
this is like implementing lodash.throttle but you also need to be a ES module specialist
Maybe this could be an npm library?
18:41
<ljharb>
eemeli: yes, and we would regardless of this proposal
18:44
<ljharb>
shu: i checked and the HTML spec already requires doing that, i think?
18:44
<shu>
no it doesn't?
18:45
<ljharb>
https://html.spec.whatwg.org/#structureddeserialize, step 21
18:45
<shu>
where do you read that it does
18:45
<shu>
wait what are you responding to?
18:45
<eemeli>
So if extractors or pattern matching would provide for an Error[Symbol.customMatcher] "regardless", does it make sense to advance a separate proposal on this?
18:45
<ljharb>
"be a real subclass" is the same as "has the expected slots and [[Prototype]]"
18:45
<ptomato>
{ isError() { return true; } } yup that's a native Error
18:46
<shu>
"be a real subclass" is the same as "has the expected slots and [[Prototype]]"
those are not deserializing DOMExceptions
18:46
<ljharb>
So if extractors or pattern matching would provide for an Error[Symbol.customMatcher] "regardless", does it make sense to advance a separate proposal on this?
yes, because having a predicate is the more basic component
18:46
<shu>
those steps are deserializing actual JS errors, not subclassed
18:46
<ljharb>
oh i don't care about domexceptions
18:46
<shu>
i do
18:46
<Jack Works>
Maybe this could be an npm library?
might be better to PR webpack and rollup
18:46
<littledan>
So if extractors or pattern matching would provide for an Error[Symbol.customMatcher] "regardless", does it make sense to advance a separate proposal on this?
yes, that's an excuse we can use to stash the check there -- Jordan has generally been requesting that proposal authors find a way to make such an API (though it has to serve some sort of dual use case today, due to this set of arguments we're having)
18:46
<ljharb>
i mean that i'm 100% fine with domexceptions passing this predicate, or not. what would you prefer?
18:47
<littledan>
yes, that's an excuse we can use to stash the check there -- Jordan has generally been requesting that proposal authors find a way to make such an API (though it has to serve some sort of dual use case today, due to this set of arguments we're having)
I agree that [Symbol.customMatcher] should make this easier
18:47
<shu>
i mean that i'm 100% fine with domexceptions passing this predicate, or not. what would you prefer?
those are my topics, i'll discuss it then
18:47
<Jack Works>
i do
HostIsErrorLike
18:48
<eemeli>
yes, that's an excuse we can use to stash the check there -- Jordan has generally been requesting that proposal authors find a way to make such an API (though it has to serve some sort of dual use case today, due to this set of arguments we're having)
Fair point; makes this sound appropriate for Stage 1.
18:48
<shu>
yes, we can do HostIsErrorLike. but look, like, 90% of the time, when i ask a question, i'm not also implicitly asking "how do we do this mechanically". most things are possible to do! i'm asking if it's a good idea
18:49
<Jack Works>
then no, DOMException does not inherit Error and missing some property Error have
18:50
<Michael Ficarra>
@littledan regarding "What policies has TC39 adopted?" do you think we need to cover this during Jordan's limited time?
18:50
<bakkot>
if no code can distinguish between "a real error" and "not a real error", why would you ever need to know the answer to that question when debugging?
18:50
<bakkot>
"I need this for debugging" is confusing to me
18:50
<shu>
i am also confused by this point
18:50
<Jack Works>
then no, DOMException does not inherit Error and missing some property Error have
Oh no longer the case. In my memory, in the past, a DOMException has no "stack" (although stack is not standarized) property
18:52
<Jack Works>
then yes, DOMException is a normal sense of "Error". but by that mean, if I made my own Exception, is it a "Error"
18:54
<Jack Works>
but why we should care this... I'll just log/report anything thrown to me, without categorize them
18:57
<littledan>
@littledan regarding "What policies has TC39 adopted?" do you think we need to cover this during Jordan's limited time?
No, I'm fine to skip it
18:57
<littledan>
but it's kinda funny that we have these dualing policies asserted by Jordan and Mark, and these are not written down and just enforced personally by them... we have to get out of this situation by documenting shared policies that we agree on
18:58
<Michael Ficarra>
hopefully after this meeting we will have a place to put them: https://github.com/tc39/how-we-work/pull/136
18:59
<Michael Ficarra>
everybody just say yes to everything Kevin suggests and then we will have a normative-conventions.md on Friday
18:59
<hax (HE Shi-Jun)>
yeah, DOMException is strange, domException instanceof Error is true, but they do not have all properties of Error (eg. no stack?)
18:59
<Jack Works>
yeah, DOMException is strange, domException instanceof Error is true, but they do not have all properties of Error (eg. no stack?)
it has stack now
19:00
<bakkot>
(new DOMException).stack // undefined
19:02
<littledan>
IIRC it differs between browsers whether you get the stack when allocating the exception or when it's thrown
19:05
<eemeli>
I'm getting a stack from that in Firefox and Node.js.
19:06
<rbuckton>
Chakra did not set .stack during construction, but during throw. Setting .stack during construction was a V8 contrivance, I think, that was later adopted by some other implementations.
19:07
<rbuckton>
So try { throw new DOMException(); } catch (e) { e.stack; } would still show a stack in Chakra
19:08
<Jack Works>
on firefox
19:17
<bakkot>
yeah looks like this is browser-specific
19:17
<bakkot>
fun!