The intention is for the second argument to enable combining hash values. Perhaps a better design would have been for hash
to return an opaque value instead of an UInt
value:
"""
HashValue
Constructed by [`hash`](@ref), opaque.
"""
struct HashValue
v::UInt
end
"""
hash(::Any, ::HashValue)::HashValue
...
"""
function hash end
Then there would be no possibility for misuse.
That said, sometimes it’s OK to use a constant seed as the second argument for hash
, perhaps saving a few operations.
In practice, it can be OK, but best practice is to avoid this. Here’s an example type implementation:
struct OrderedPair{A, B}
a::A
b::B
end
function Base.:(==)(l::OrderedPair, r::OrderedPair)
all(==, (l.a, l.b), (r.a, r.b))
end
const ordered_pair_hash_seed = hash(0x3e44a45c2c9ad95b58036c78cd49f6d6)
function Base.hash(pair::OrderedPair, h::UInt)
a = hash(pair.a, h)
b = hash(pair.b, a)
hash(b, ordered_pair_hash_seed)
end
Here’s a type that might benefit from a different kind of combination of hash values, one that’s commutative:
struct UnorderedPair{A, B}
a::A
b::B
end
function Base.:(==)(l::UnorderedPair, r::UnorderedPair)
all(==, minmax(l.a, l.b), minmax(r.a, r.b))
end
const unordered_pair_hash_seed = hash(0x464ec8ef89d4d738ae8280142b46ee74)
function Base.hash(pair::UnorderedPair, h::UInt)
a = hash(pair.a, h)
b = hash(pair.b, h)
c = xor(a, b)
hash(c, unordered_pair_hash_seed)
end
This works even for the hypothetical HashValue
-based design, as long as we have a method like this, because xor
is commutative:
function xor(l::HashValue, r::HashValue)
xor(l.v, r.v)
end
EDIT: made an issue regarding HashValue
: safer, more foolproof `hash`: make `hash` return an opaque value, and take that type as the second argument · Issue #57055 · JuliaLang/julia · GitHub