But the Element
type of your BloomFilter
here is not S
or WrappedS
—it's the type of the objects which you are add
-ing and maybeHas
-ing, i.e., String
. Your S
and WrappedS
are still relying on the underlying guarantee that the 'same' value will always produce the same hash value. In the case of S
this arises because we want that two S
values containing the same String
will always produce the same hasher state when they are hashed as part of WrappedS
, and WrappedS
further relies on this guarantee to ensure that given the 'same' S
and the 'same' salt i
, you'll produce the same index for bools
every time.
That you've deliberately broken the implementation for ==
while also ensuring it doesn't get called isn't really material here. The implementation of BloomFilter
here only works because of the semantic guarantees imposed on String
(and transitively, S
) by the combination of Equatable
and Hashable
.
To look at this another way, suppose that String
exposed a public capacity
property that gave the current capacity of the underlying buffer, and we implemented your example exactly as above, except that we implemented S.hash(into:)
as:
func hash(into hasher: inout Hasher) {
hasher.combine(string)
hasher.combine(string.capacity)
}
Clearly, this gives us a broken BloomFilter
implementation because we could potentially have:
var s = "Hello"
filter.add(s)
s.append("!")
s.removeLast()
filter.maybeHas(s) // false
But how can you explain what's broken here without invoking the notion of 'sameness'? And how would you describe what's broken in the following implementation of WrappedS.hash(into:)
without invoking the same?
func hash(into hasher: inout Hasher) {
hasher.combine(s)
hasher.combine(salt)
hasher.combine(Int.random())
}
My point, ultimately, is that you haven't really implemented Hashable
without Equatable
, because there are still very important properties that you want your Hashable
implementation to have in order for BloomFilter
to work properly. And you'll find that if you try to describe what those properties are, it's precisely a description of the relationship between Hashable
and the correct implementation of Equatable
for your types.
ETA: Yet another way to think about this is that the relationship between Equatable
and Hashable
is about more than just particular implementations of protocol requirements. It's about the fundamental idea that hashability requires you to invoke a notion of equality which agrees with your hash value in some way. Yes, it's possible to write algorithms which depend only on hashability and never directly invoke equality, but such algorithms still depend on the underlying equality notion in order to work properly.