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?

1 Like

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.

:man_shrugging: Whenever you want to know that two structures are equivalent?

julia> isequal(Ref(1), Ref(1))

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.

1 Like

FWIW, there’s a classic Lisp article on why multiple copy / equality implementations are needed:


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
    return true

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!
        Base.@nexprs $N i -> (getfield(a, i) == getfield(b, i) || return false)
        return true

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.

1 Like