18:20 | <danielrosenwasser> | https://1drv.ms/b/s!AltPy8G9ZDJdq3JSrN6Dh1XYVwpW |
22:08 | <Michael Ficarra> | jschoi: you could start by proposing a SameValueSet and SameValueMap, though I doubt it would be too popular |
22:09 | <Michael Ficarra> | I continue to believe that these data structures should've used SameValue, and this is a perfect example of why |
22:10 | <Michael Ficarra> | you could easily have the current behaviour by replacing -0 with 0 before doing any map operations, but you can't easily go the other way around |
22:11 | <Mathieu Hofman> | We you can easily replace with a unique symbol |
22:11 | <Mathieu Hofman> | That's what I've done when I needed to support -0 in Map/Set |
22:11 | <Michael Ficarra> | as long as that symbol doesn't escape, which you can't guarantee when the map is user-provided |
22:12 | <Mathieu Hofman> | if the map is user provided, sure |
22:12 | <Mathieu Hofman> | I'm talking in the case of wrapping a map |
22:12 | <Michael Ficarra> | also jschoi has use cases in mind like the user priming the cache, which again wouldn't work with a special replacement symbol |
22:12 | <Mathieu Hofman> | which as we agreed was the only way to preserve that kind of invariants |
22:13 | <Michael Ficarra> | yeah I used the strategy of wrapping two sets in https://www.npmjs.com/package/samevalueset, but a symbol probably would've worked fine there |
22:15 | <Mathieu Hofman> | That said, I'd love if we could have a comparison predicate configurable when building the map, but to no shoot down performance, we may need to expose SameValueZero as a predicate the same way SameValue is exposed as Object.is , so that not providing a comparison predicate isn't magical. |
22:17 | <Michael Ficarra> | const SameValueZero = (a, b) => a === 0 && b === 0 || Object.is(a, b); |
22:19 | <Michael Ficarra> | are you saying that passing that in would be too slow because it's not built-in? |
22:21 | <bakkot> | passing it in fundamentally does not work because that means you have to do a comparison against every element |
22:21 | <bakkot> | you need a user-defined hash function as well; just having user-defined equality is not useful |
22:21 | <Mathieu Hofman> | Why so complicated? const SameValueZero = (a, b) => a === b , but the point is that this wouldn't be optimizable as the implementation has to call the user predicate instead or recognizing it as its own |
22:22 | <nicolo-ribaudo> | NaN |
22:22 | <Michael Ficarra> | not sure if trolling... |
22:23 | <Mathieu Hofman> | Oh right, ugh I'm tired |
22:23 | <Michael Ficarra> | we all are after this week |
22:23 | <Mathieu Hofman> | Ok nvm forget all of the above |
22:28 | <Mathieu Hofman> | yeah in that case the only other alternative I can think of if would be providing mapping functions that transform keys before insertion, probably with a SameValue based caching mechanism to be robust against instability of the transform function if the initial key is already known |
22:29 | <Mathieu Hofman> | which seems not very efficient |
22:30 | <Ashley Claymore> | yeah I used the strategy of wrapping two sets in https://www.npmjs.com/package/samevalueset, but a symbol probably would've worked fine there -0 , then checked the code. Using two sets to also preserve the insertion order is a classy touch! |
22:32 | <ljharb> | efficiency is nice, but the capability is more important |
22:32 | <ljharb> | what's the typical range of map/set content counts? |
22:33 | <ljharb> | are many people making maps and sets with a million+ things, in a hot path, and who also want this extra capability? |
22:40 | <Kris Kowal> | I think tens of thousands is a pretty reasonable estimate for a production-sized Map in some Node.js applications. |
22:43 | <Michael Ficarra> | if they're backing their memoisation implementation with it, it could see a lot of content |
22:44 | <ljharb> | and would any of these use cases (which obviously exist; i'm asking about %) need any of the functionality from these hooks? |
22:44 | <ljharb> | because if so, they're probably already paying the perf cost, and if not, they presumably wouldn't pay it (we wouldn't want to add a feature that penalized existing code paths, obv) |
22:45 | <bakkot> | I think a lot of people would be very surprised if using a hook made set membership O(n) instead of ~constant |
22:46 | <bakkot> | you don't need large sets for that to be a problem, you just need to be doing lots of queries |
22:46 | <bakkot> | like if you don't want fast membership tests, you can just use a list and .find . which is what this would amount to anyway |
22:48 | <jschoi> | jschoi: you could start by proposing a SameValueSet and SameValueMap, though I doubt it would be too popular -0 is probably uncommon enough that we can punt doing so to a userland Map-like class. But at least it would be possible, if memo takes a Map-like cache argument. I’m already going to propose LRUMap and friends. |
22:48 | <shu> | efficiency is nice, but the capability is more important |
22:48 | <shu> | or map, i guess this conversation is about map |
22:48 | <ljharb> | if that is the case just write your own set? |
22:49 | <Michael Ficarra> | jschoi: I guess? It's a design worth considering, but it feels a bit icky for default memoise to do any coalescing. |
22:49 | <bakkot> | i am very, very strongly opposed to having hooks which cause set membership querying to be O(n) |
22:49 | <bakkot> | we just... we cannot do that |
22:49 | <bakkot> | the whole point of the data structure is to have Not That |
22:50 | <shu> | ergonomics is a bad reason to slow down cases that do require performance |
22:50 | <jschoi> | jschoi: I guess? It's a design worth considering, but it feels a bit icky for default memoise to do any coalescing. |
22:50 | <Michael Ficarra> | jschoi: no, for the reasons we're talking about above |
22:50 | <shu> | i'm slowly being radicalized into something more extreme, which is that ergonomics is a bad argument |
22:52 | <ljharb> | i am very, very strongly opposed to having hooks which cause set membership querying to be O(n) |
22:52 | <shu> | i am very, very strongly opposed to having hooks which cause set membership querying to be O(n) |
22:52 | <ljharb> | for example, instead of a "predicate" hook, a "comparison value" hook, that by default is x => x but could be made to return a string, or a unique symbol, or something |
22:52 | <ljharb> | i assume that would have potential memory costs but little to no lookup costs |
22:53 | <Kris Kowal> | My position is approximately that more interesting Maps and Sets can be created in user code by composition, without inheritance, using what we already have, and that record and tuple keys will just make that easier. |
22:54 | <Mathieu Hofman> | Right that's what I suggested earlier, but to prevent bugs due to instability of the mapping function, you have to cache its results |
22:54 | <ljharb> | My position is approximately that more interesting Maps and Sets can be created in user code by composition, without inheritance, using what we already have, and that record and tuple keys will just make that easier. Map.prototype.set.call will continue to not work properly on these userland maps |
22:54 | <bakkot> | sure they will |
22:54 | <bakkot> | well |
22:54 | <bakkot> | it will throw |
22:54 | <ljharb> | Right that's what I suggested earlier, but to prevent bugs due to instability of the mapping function, you have to cache its results |
22:54 | <bakkot> | it won't violate any invariants |
22:54 | <bakkot> | so, seems fine |
22:54 | <shu> | why do you want built-in map methods to work for your userland maps? |
22:55 | <ljharb> | the same reason i want built-in iteration syntax to work for userland iterables? |
22:55 | <Kris Kowal> | why do you want built-in map methods to work for your userland maps? |
22:55 | <bakkot> | those are... very... different |
22:55 | <shu> | the same reason i want built-in iteration syntax to work for userland iterables? |
22:55 | <ljharb> | to me they're all conceptual protocols |
22:55 | <ljharb> | i want to be able to work with map-like collections in a robust fashion |
22:55 | <bakkot> | just call .set |
22:55 | <bakkot> | that is how you work with map-likes |
22:56 | <Mathieu Hofman> | The protocol in this case is .get , .set , .delete ? |
22:56 | <ljharb> | lol i can see none of you are going to react well to my preferences here |
22:56 | <Kris Kowal> | I mean, I love protocols. |
22:56 | <ljharb> | in general, i want to minimize how much i trust other code. |
22:56 | <ljharb> | "just call .foo on the argument" is not doing that |
22:57 | <jschoi> | My position is approximately that more interesting Maps and Sets can be created in user code by composition, without inheritance, using what we already have, and that record and tuple keys will just make that easier. Speaking of tuples, has there been any recent activity around composite keys (which are kind of an alternative to tuple keys)? I ask because someone has suggested them for https://github.com/js-choi/proposal-function-memo/issues/4#issuecomment-1083552333, and I don’t see any activity around them since 2019. The alternative for memo would be to use argument tuples as keys in the Map-like cache and to replace objects in the tuple with symbols from a WeakMap. |
22:58 | <ljharb> | lol i am leaving this convo |
22:58 | <shu> | excuse me? |
22:58 | <Mathieu Hofman> | Unfortunately I hear there is push back against considering tuples of unique symbols as WeakMap keys ... |
22:58 | <ljharb> | because i don't value any of the motivations for either of those, personally, but that should mean i stay out of the conversation, not that i ridicule or block them |
22:59 | <Kris Kowal> |
|
22:59 | <ljharb> | i think i'm going to step out of the convo myself; this is reminding me of the completely BS response i get to "brand checking" desires, and i'm a bit too tired to handle it well. |
23:00 | <jschoi> | Unfortunately I hear there is push back against considering tuples of unique symbols as WeakMap keys ... Yeah, fortunately in this case, the object-replacement symbols would be the WeakMap’s values, not its keys. See https://github.com/js-choi/proposal-function-memo/blob/main/README.md#result-caches. But we couldn’t use a WeakMap directly as the cache, though, if we used tuple keys, since tuples can’t contain objects anyway. But that’s fine. We’re using user-provided Map-likes anyway. |
23:00 | <shu> | because i don't value any of the motivations for either of those, personally, but that should mean i stay out of the conversation, not that i ridicule or block them |
23:01 | <shu> | my reaction is not because "lol i don't care about robustness, get out of here with that proposal" |
23:01 | <jschoi> | We have certainly had conversations about records and tuples as recently as last December in the SES calls https://docs.google.com/document/d/1FZ95-NZIQE9fw3A8Sgcz2BKep6MlC_Kng0dlf1ehabQ/edit#heading=h.kf3ndl27679m |
23:02 | <Kris Kowal> | in general, i want to minimize how much i trust other code. |
23:03 | <Kris Kowal> | But I do like collection protocols, though there is a design tension in performance. |
23:03 | <Kris Kowal> | Much depends on what kind of contract you’re providing. |
23:04 | <Kris Kowal> | In the situations where we find ourselves uncurryThis(String.prototype.set) , the contract is not object oriented, not lazy bound, instances must be Sets and anything else is going to be ignored. |
23:05 | <Kris Kowal> | Whereas Array.prototype.slice.call(arguments) is from a very different design space. |
23:05 | <Kris Kowal> | And I like both of these subjectively. |
23:07 | <Kris Kowal> | The one thing that Map and Set do that I can’t do in user code is fast hashing on an opaque object, so I’d turn to them as a high performance building block for user code collections with rich protocols. |
23:07 | <Kris Kowal> | Thus, cake and eat it. |