00:08
<rbuckton>
yeah i don't agree with the prediction
I'd be more inclined to agree if we weren't making it so convenient. If these were just additions to built-in iterators and maybe generators, I'd be less concerned. Introducing a global Iterator and an adapter like Iterator.from feels more like TC39 blessing this as "the way", which I'm not comfortable with.
00:13
<rbuckton>
Augmenting IteratorPrototype and making it easier to reach for just feels like another opportunity for Array.prototype-like patching that we've been dealing with for years, especially if people start feeling like the helpers available aren't comprehensive enough.
00:18
<snek>
if pipeline moved forward
00:18
<snek>
i wouldn't be wholly against functions
00:18
<snek>
but without that there's no point even discussing it
00:59
<rbuckton>
As much as I still prefer F#-style, if pipeline could settle on a topic I'd be happy.
01:01
<rbuckton>
temporal achieved this by being large and complex
Temporal is amazing for constructing date, time, and tz info. Its API for comparing dates/times is unfortunately ungainly since there's no mechanism for operator overloading, and the fact that .now() is detached into its own thing is exceedingly frustrating the times I've used it.
01:02
<rbuckton>
It's like 10 steps forward and 5 steps back
01:05
<rbuckton>

Determining whether an Instant occurs in the past is painfully verbose:

Temporal.Instant.compare(instant, Temporal.Now.instant()) < 0

But it seems like the API design is very focused on "only one way to do it", regardless of convenience.

01:09
<ptomato>
I don't like Temporal.Now either! but it's always been a hard requirement from the plenary AFAIU
01:14
<ptomato>

Determining whether an Instant occurs in the past is painfully verbose:

Temporal.Instant.compare(instant, Temporal.Now.instant()) < 0

But it seems like the API design is very focused on "only one way to do it", regardless of convenience.

I hope nothing I've said caused you to get this impression; eliminating multiple ways to achieve the same thing is not something we've spent time on at all! there's no idiomatic isBefore / isAfter method because we had to draw the line somewhere
01:14
<ptomato>
you may want to subscribe to https://github.com/js-temporal/proposal-temporal-v2/issues/6
01:14
<snek>
its interesting that compare is not an instance method
01:15
<rbuckton>
I think a more general compareTo is better.
01:15
<rbuckton>
its interesting that compare is not an instance method
Its good as a static method for use with .sort()
01:15
<snek>
but either way i'm fine with this, people who care deeply about temporal apis are already working on this as has been linked 😄
01:16
<rbuckton>
you may want to subscribe to https://github.com/js-temporal/proposal-temporal-v2/issues/6
IIRC I commented on a thread about this in the proposal repo at one point
01:17
<snek>
Its good as a static method for use with .sort()
yeah personally i'd say just write an arrow function
01:17
<snek>
but this is the sort of thing
01:17
<rbuckton>
Or maybe it was in matrix. I can't find the issue.
01:17
<snek>
i much prefer it existing at all
01:17
<snek>
to it going back and forth based on my opinion
01:17
<snek>
so 🤷
01:20
<rbuckton>

I really want to introduce generic comparison (not operators) at some point, i.e.:

interface Equaler {
  equals(a, b): boolean;
  hashCode(x): number;
}
interface Comparer {
  compareTo(a, b): number;
}

And allow you to pass an Equaler to a Map/Set constructor, and add things like a SortedMap/StortedSet like I put together in @esfx/equaler, @esfx/collections-hashmap, @esfx/collections-hashset, @esfx/collections-stortedmap, @esfx/collections-sortedset (which I wrote as a test bed to test out the implementation).

01:21
<rbuckton>
I'd love to be able to have a Map whose keys are URL or Temporal.Instant without having to coerce to a string.
01:21
<Kris Kowal>
I’ve done similar work with only slightly different names.
01:22
<Kris Kowal>
We had a relevant conversation about deep equality at the most recent SES meeting.
01:22
<bakkot>
Augmenting IteratorPrototype and making it easier to reach for just feels like another opportunity for Array.prototype-like patching that we've been dealing with for years, especially if people start feeling like the helpers available aren't comprehensive enough.
The patching of Array which we occasionally run into is almost entirely pre-ES6 libraries. People are not at all doing that these days, from what I can tell. So I don't really share this concern. Evangelism about not doing that worked, eventually, especially once we started actually adding useful stuff ever.
01:23
<Kris Kowal>
SES meeting on deep equality, where we differentiate structural deep equality and deep equality protocols https://youtu.be/z_gXDSYKlWI
01:24
<Kris Kowal>
Equality, hash, and compare protocols are in http://www.collectionsjs.com/
01:25
<Kris Kowal>
(CollectionsJS isn’t a viable project in its current form because I mispredicted some Array shims. Ooops.)
01:26
<rbuckton>
@esfx/equatable has definitions for equality, comparability, structural equality, and structural comparability.
01:27
<rbuckton>
Though my most recent npm publish (today) has a few issues I'm trying to fix. I just switched the hashCode generation to use native code on Node when possible and that's had some hiccups.
01:28
<Kris Kowal>
Adding protocols to the standard library probably means different Map and Set implementations or a very clever system for overloading their behavior.
01:29
<snek>
i really wish we had a System.hash(v) api
01:29
<Kris Kowal>
Collections went the cheap route of random numbers and a weak map. It predates intrinsic Map and Set, so there it does some odd stuff. But, not surfacing information divined from pointers is important.
01:29
<snek>
so we could write our own collections
01:30
<rbuckton>
Agreed. @esfx/equatable exposes a rawHash(v) API which is essentially the same.
01:30
<snek>
does that hash structurally or based on js identity
01:30
<rbuckton>
I don't use pointers, I use the same hash identity that V8 uses for objects as keys in a map.
01:30
<rbuckton>
JS identity.
01:30
<snek>
oh its a native fn?
01:31
<Kris Kowal>
Yeah, I suspect that’s still a side channel.
01:31
<snek>
feel free to delete System.hash :P
01:31
<rbuckton>
oh its a native fn?
When I can get away with it, yeah. Otherwise its a WeakMap and a random number as a fallback.
01:31
<snek>
oh nice
01:31
<snek>
that's basically how v8 works anyway
01:32
<snek>
well not a weakmap but random numbers yes
01:32
<rbuckton>
At least, as soon as I finish wrangling breaking changes in my monorepo
01:35
<snek>
Yeah, I suspect that’s still a side channel.
thinking about this more i don't think hash is a side channel
01:36
<snek>
you can't communicate with it
01:37
<bakkot>
as long as it's mixed with an actually random number
01:37
<snek>
i think every js engine that cares about this sort of thing does that anyway
01:37
<snek>
due to hash collision attacks
01:38
<bakkot>
yeah and because getting the address of an object in memory is one gadget which is used as a step in a lot of exploits
01:38
<snek>
oh right yeah do not use the address lmao
01:39
<bakkot>
I suspect none of the major engines would do it but if were were adding something like this we'd need to have a big loud note about including a good random value as a component
01:39
<snek>
i hear 4 is a great random number
01:39
<bakkot>
and it would be another source of nondeterminism, which... we should figure out a better story about adding those in general
01:41
<snek>
hmmmmmmMMMMMM
01:41
<snek>
actually no i'm dumb
01:41
<snek>
i was gonna say "does the hash actually have to be a transparent value" and then i realized how useless it would be if it was opaque lul
01:47
<rbuckton>
It needs to be a number value (preferably uint32) to bucketize things efficiently.
02:15
<snek>
for the constraints of js i think it must be in u32 range otherwise you can't modulo it
02:15
<snek>
or bitwise it
02:19
<rbuckton>
and u64 is outside the range of allowable array indices
02:32
<rbuckton>

ok, I think I fixed my breaking changes. @esfx/equatable defines the following:

  • Equatable - A "protocol" involving two symbol-named methods ([Equatable.equals](other) and [Equatable.hash]()) indicating an object defines its own equality mechanism.
  • Comparable - A "protocol" involving one symbol-named method ([Comparable.compareTo](other)) indicating an object defines its own comparability mechanism.
  • StructuralEquatable - A "protocol" for [StructuralEquatable.structuralEquals](other, equaler) and [StructuralEquatable.structuralHash](equaler)
  • StructuralComparable - A "protocol" for [StructuralComparable.structuralCompareTo(other, comparer).
  • Equaler - An "interface" describing an object with { equals(a, b), hash(x) } methods, as well as a namespace object with default implementations (defaultEqualer, structuralEqualer, tupleEqualer (actually compares arrays), tupleStructuralEqualer), a create helper method to create an Equaler from callbacks, and a combineHashes function to help with complex hashCode construction (via bit shifting and XOR).
  • Comparer - An "interface" describing an object with { compare(a, b) } methods, as well as a namespace object with default implementations (defaultComparer, structuralComparer, tupleComparer, and tupleStructuralComparer).

There's also a @esfx/equatable-shim package that shims the Equatable and Comparable interfaces onto built-ins (and all of those methods are symbol-named, so no issues like with .flatten, etc.)

02:33
<Kris Kowal>
The CollectionsJS deep equality protocol carried an ibid map to break cycles and allowed an override for the contentEquals of the children.
02:33
<rbuckton>
I use the equality and comparison interfaces pretty heavily in the @esfx/collections-* packages as well as the @esfx/iter-* and @esfx/async-iter-* packages.
02:34
<rbuckton>
Mine doesn't do ad-hoc structural equality, but rather expects something that would be structurally equal to something else to explicitly implement that structural equality.
02:35
<rbuckton>
I haven't had much cause to use that part though, about 99% of my use cases have been shallow equality via custom Equaler and Comparer objects.
02:38
<rbuckton>
If I were to propose to TC39, I'd primarily focus on Equaler ({ equals(a, b), hash(x) }) and Comparer ({ compare(a, b) }) support in collection classes, default implementations of each that would align with existing behavior in Map and Set for keys, and a hash function reachable from somewhere (even if it's just Equaler.defaultEqualer.hash).
02:40
<rbuckton>
After that, maybe Symbol.equals, Symbol.hash, and Symbol.compareTo to hook equality and comparability on an instance. But at no point would any of that affect relational operators like ==, >, etc. (overloading would be far out of scope).
03:44
<ljharb>
Doesn’t it return an iterator instance that has the slots of a generator?
03:58
<bakkot>
not exactly? it returns an iterator instance that has the slots of an iterator helper
03:58
<bakkot>
which is not the same thing as a generator
04:00
<bakkot>
like you can't (function*(){})().next.call(that helper thing); that will not work
04:01
<bakkot>
I guess to be precise, the way it's written down it does have the same slots as a generator, but that's not actually observable by anything, just an editorial convenience
04:05
<bakkot>
anyway it is probably easier to talk about this in terms of observable behavior. there is very little observable difference between Iterator.from(x) and Iterator.from(x).map(a => a). if there is a specific difference in observable behavior between the two you are worried about, post a code snippet and I can tell you what it does?