We have deepcopy
, which recursively copies the object down to its “atoms”. Has anyone written the corresponding deepequal
, such that deepequal(deepcopy(x), deepcopy(x)) == true
?
Under what circumstance would deepequal
yield different results to equal
? For deepcopy
, presumably you would want to change some component and not affect the object you copied from.
But I can’t think of a reason why you would need a deepequal
.
Whenever you want to know that two structures are equivalent?
julia> isequal(Ref(1), Ref(1))
false
That would be deepequal
.
I think it’s a bit like deepcopy
. Not super clean semantically, but can be very useful for testing/debugging/rapid development.
FWIW, there’s a classic Lisp article on why multiple copy
/ equality implementations are needed: P.S.: The Best of Intentions
Not that I know of. FWIW, I think that generally ==
or isequal
should be used by most user code, and types should just define the relevant methods.
That said, I often define custom equality operators (eg for unit testing), it is pretty easy to do recursively. Cf fieldnames
for struct
, use in a generated function.
There doesn’t exist one, but you can easily define your own if you really need it. The following function is one I’ve found useful in the past to extend ==
for my own types:
function deepequal(a, b)
typeof(a) == typeof(b) || return false
N = fieldcount(typeof(a))
for i in 1:N
getfield(a, i) == getfield(b, i) || return false
end
return true
end
I also just cooked up the following which is more generic, but haven’t tested it very thoroughly, and really don’t know if it’s really worth it to write as a @generated
function…
@generated function deepequal(a, b)
# check the types
a == b || return :(false)
N = fieldcount(a)
# fallback to regular comparison for primitive types
N == 0 && return :(a == b)
# unroll the loop, because it's cool!
quote
Base.@nexprs $N i -> (getfield(a, i) == getfield(b, i) || return false)
return true
end
end
edit: actually, for what you want both functions should be recursive. The comparisons should be deepequal(getfield(a, i), getfield(b, i))
rather than ==
Thanks! I thought of writing my own, but the deepcopy internals are surprisingly complex. Eg. Your deepequal
would fail with circular structures, and it might fail with arrays/dicts. I was hoping someone had provided it in a package or something.
Note that Ref(1) == Ref(1)
falls back to ===
by default. You can check this with julia> @edit Ref(1) == Ref(1)
. Most regular containers check their content already (e.g. [1] == [1]
), so if you define ==
on your type to check its own fields as well, it should work out fine. Just make sure to special case circular dependencies or disallow your type to contain itself.