Closed goretkin closed 1 year ago
It seems to me that Julia's compiler optimize enough to get by with regular instead of generated functions. The following code appears to work well. Both the comparison (using ==
) and the hash are as fast as in AutoHashEquals.jl
. (Empty structs also work.)
_hash(x, h, ::Val{0}) = h
function _hash(x, h, ::Val{i}) where i
_hash(x, hash(getfield(x, i), h), Val(i-1))
end
macro auto_hash_equals(def)
@assert def isa Expr && def.head === :struct
T = def.args[2]
T isa Expr && (T = T.args[1])
quote
$(esc(def))
function Base.:(==)(x::$(esc(T)), y::$(esc(T)))
all(1:fieldcount(typeof(x))) do i
getfield(x, i) == getfield(y, i)
end
end
function Base.hash(x::$(esc(T)), h::UInt)
_hash(x, hash($(esc(QuoteNode(T))), h), Val(fieldcount(typeof(x))))
end
end
end
EDIT: made hash
faster
@auto_hash_equals
now permits the user to specify which fields are significant. While it might still be possible to use this kind of coding pattern, the benefit is mainly to the package developer (me). I would welcome a PR if someone wanted to offer simpler code with the same performance, but I'm not tempted to spend the time to do it myself. So I'm going to close this issue.
I like the idea of reusing methods on
Tuple
for hashing and==
,isequal
, which leaves a question of how to turn a struct into a tuple of its fields. What I did in https://github.com/andrewcooke/AutoHashEquals.jl/pull/21 usesntuple
, but there is an alternative using generated functions, which produces better LLVM code, though I did not detect any performance difference I discussed it hereCurrently, the macro is defined to take a struct definition and extract the field names given the definition.
getfield
can just take a field index, so parsing the names is not necessary. We should be able to avoid parsing the struct definition at all, and just usefieldcount
in a generated functionWhat if this package instead worked like:
This has the benefit of not interfering with other struct-level macros (e.g. what https://github.com/andrewcooke/AutoHashEquals.jl/pull/19 tries to address). Other struct-level macros might want to define inner constructors, but that's not the case for this package.