doc: add CompareOptions.filterer#18
Conversation
gibson042
left a comment
There was a problem hiding this comment.
I'm a little torn on this, but leaning towards opposition. The only advantage of passing in a predicate rather than applying it to iterator output is that it allows filtering to affect boolean output. But it comes at the cost of making that mode intertwine implementation code with user code, which almost always incurs a performance penalty. I think it's easy enough to get boolean output like const isDifferent = !!deviations.filter(filterer).next().value, and if that much ceremony is too unergonomic then we can push to fix it with a new iterator helper, e.g. const isDifferent = !deviations.filter(filterer).isEmpty().
|
|
||
| ```ts | ||
| type CompareOptions = { | ||
| filterer?: (a: unknown, b: unknown) => boolean, |
There was a problem hiding this comment.
What inputs are expected for a and b? If they're values in the object graph, then how does a filterer differentiate
- extra vs. missing vs. mismatch
- property descriptor
- prototype
or just in general, where in the graph its invocation relates to?
There was a problem hiding this comment.
Ah, yes, I suppose the type of A and B are not unknown: they're a primitive (or "special" key). And a couple things are missing.
So I guess the type would be more like:
| filterer?: (a: unknown, b: unknown) => boolean, | |
| filterer?: ( | |
| a: Primitive, | |
| b: Primitive, | |
| path: (string | Symbol)[], | |
| reason: 'extra' | 'missing' | 'mismatch', | |
| ) => boolean, |
For descriptor, I think we already covered this: A and/or B would have that special key in path (which was missing before, so that probably created the confusion).
RE the "special" path segment, what about a well-known symbol, like Symbol(Deviation:Descriptor) & Symbol(Deviation:Prototype) (or Symbol(DescriptorDeviation) & Symbol(PrototypeDeviation))? I think I'd prefer the : delimited version.
Thoughts on mismatch → unequal, equality, or maybe even the equality algo used like same-zero? mismatch seems a little "shit happened" to me 😅
There was a problem hiding this comment.
RE the "special" path segment, what about a well-known symbol, like
Symbol(Deviation:Descriptor)&Symbol(Deviation:Prototype)(orSymbol(DescriptorDeviation)&Symbol(PrototypeDeviation))? I think I'd prefer the:delimited version.
The special path segment can't be a symbol, because any such symbol could itself be an actual property key—e.g., { foo: { [Symbol(DescriptorDeviation)]: { value: 42 } } } would have an value-mode path like ["foo", Symbol(DescriptorDeviation), "value"] which could not be distinguished from a similar descriptor-mode path for { foo: 42 }.
Thoughts on
mismatch→unequal,equality, or maybe even the equality algo used likesame-zero?mismatchseems a little "shit happened" to me 😅
Yeah, I don't love "mismatch" either. "unequal" works for me.
There was a problem hiding this comment.
Symbols are unique though, so Symbol(foo) ≠ Symbol(foo). If the symbol we use is well-known, it can be checked and there is no collision.
There was a problem hiding this comment.
You can't stop someone from extracting that symbol and using it as a property key.
There was a problem hiding this comment.
Ahh. That's true… but then they clearly effed up, no? They broke themselves.
There was a problem hiding this comment.
Indeed, there does not exist any value in JS that can be a "sentinel" value, because every such value could somehow appear in a user's object.
This claim is true but irrelevant here; we're talking about an array of property keys so objects are in fact available as sentinels.
Ahh. That's true… but then they clearly effed up, no? They broke themselves.
No, that's not clear at all. I am not willing to write off such use as "broken".
There was a problem hiding this comment.
I believe there is no legitimate use-case for a user to pass an object containing our own symbol to compare.
There was a problem hiding this comment.
Here's one: verification that an actual Symbol built-in matches expectations. There are multiple ways to do accomplish this, but at least one of them involves using the object itself (e.g., compare(globalThis.Symbol, expectedSymbol)).
There was a problem hiding this comment.
"Legitimate" isn't really relevant (although I can come up with some) - if it is possible, we have to define behavior for it, and having a special sentinel value produce confusing results just isn't viable.
A library can arbitrarily declare a subset of value types are valid, but the language can not.
This isn't about getting a boolean; it's purely about performance (and maybe a little bit about DX—but not related to boolean).
Before I opened this, I checked with Oli, who said this would likely have relatively significant perf benefits:
|
I'm extremely skeptical here. Is he saying that constructing an { actual, expected, path, reason } object is more expensive than calling a user-defined function with (actual, expected, path, reason) arguments??
Given how commonplace Trying to optimize performance at this stage just seems misguided. From my perspective, the only argument to be made for |
As noted by Justin in the May plenary, this could have quite some advantages (performance, such as avoiding the needless expense of constructing an Iterator just to immediately exclude all its items).