@hisekaldma's explanation is excellent, thanks!
Unfortunately, this is Hyrum's Law in action -- Set
's intersection methods do not document which input set the result's members come from, but they also do not randomize this, so naturally there is code out there that implicitly assumes the original behavior.
Sadly we did not realize this optimization was source-breaking.
Beta testing did not uncover any obvious binary compatibility issues arising from this change, but I imagine this is just because in most cases, Set
's methods get specialized into the client module, so existing binaries did not pick up this change.
Now that people start recompiling their code in the new stdlib in Swift 5.5, this behavioral change is evidently triggering source compatibility issues, which isn't good.
It is possible to restore the original behavior without losing the performance improvement. I think we should do that! This will include a code size regression, as we'd probably need to implement the small.intersect(large)
and large.intersect(small)
cases on two completely separate code paths.
(Unfortunately 5.5 has shipped now, which puts us in a somewhat uncomfortable position -- developers whose code is affected by this change may now be busy updating it to instead rely on the new behavior; and if so, then fixing the stdlib to restore the original will break them twice.
But if this proves to be a widespread problem, reverting to the original behavior is likely still the right thing to do.)
Note: (The rest of this post is a rant about the concept "substitutability" -- it's largely irrelevant to the topic at hand.)
Equatable
's documentation does include this passage:
Equality implies substitutabilityâany two instances that compare equally can be used interchangeably in any code that depends on their values.
However, sadly this sentence is very misleading and I find it best to ignore it. Two values that are "substitutable" in one context may have differences that are very much relevant in another. For instance, "Caf\u{c9}" == "Cafe\u{301}"
is true, but these two strings are definitely not substitutable with each other in a universal sense.
The way I look at it, Equatable
simply defines some arbitrary equivalence relation that the type's author thought to be useful. (And sometimes it's not even an equivalence relation, as is the case with standard floating point types...
)
The purpose of a Set
is to remember at most one member of each equivalence class defined by this relation, and, given a value, to quickly determine if the set already has a member of the same equivalence class. The exact value that gets remembered clearly does 100% matter -- or Set
wouldn't need to provide separate APIs for insert(_:)
and update(with:)
.
"Equality implies substitutability" is a gross oversimplification at best.