Ad-hoc equality test for struct after definition



I am trying to streamline some unit tests. Most of them work by generating a value and comparing it to a known value. I want something like ==, but I only want to define it in the tests, not the package source, since equality is not really useful for the struct per se, just unit testing (if I wanted to define it in general, I would use something like AutoHashEquals.jl).

So I just want to define an ad-hoc, ==-like comparison operator which does the following:

  1. if arguments have the same type which is a composite type, disregarding parameters, just compare all fields with ==
  2. otherwise compare the values themselves with ==.

Currently I am using

@generated function ≂(x, y)
    if !isempty(fieldnames(x)) && x == y
        mapreduce(n -> :(x.$n == y.$n), (a,b)->:($a && $b), fieldnames(x))
        :(x == y)

which works OK, except that I want it to be less strict about parametric types, eg

struct Foo{T}
Foo(1,2) ≂ Foo(1.0,2.0) # should be true, currently false

How can I do this? Also, I don’t insist on a generated function: this is what I could come up with, but better suggestions are appreciated.


Why do you use @generated functions?

This line confuses me, why do you need the && part if you have it covered by the else clause?

EDIT: just deleted my proposal for a faster version , If you remove && x == y your code works as you intended

2nd Edit: I finally understand @generated functions :slight_smile:
if you follow my first suggestion you might compare too different types as equal if they have the same number of fields
with the same name.
This solves the issue:

@generated function ≂(x, y)
    if !isempty(fieldnames(x)) && ( ==
        mapreduce(n -> :(x.$n == y.$n), (a,b)->:($a && $b), fieldnames(x))
        :(x == y)

in a generated function anything that is not quoted is just its inferred datatype, got it now.


Came here for the same reason. This is pretty cool. Maybe it should be included in Test somehow, the need for comparing only in the tests is pretty general. Like a AutoHashEquals just for Test.


The following behavior seems weird:

struct Bar
x = [Bar(i,string.(1:i)) for i in 1:5]
y = [Bar(i,string.(1:i)) for i in 1:5]
x ≂ y # -> false!
all(x .≂ y) # -> true

Also, why not replace the == in @TsurHerman’s solution with a ≂ to allow for nested structs:

        mapreduce(n -> :(x.$n ≂ y.$n), (a,b)->:($a && $b), fieldnames(x))

I might be wrong.


Try something like

@generated function ≂(x, y)
    if Base.isstructtype(x) && Base.isstructtype(y) && ( == 
        mapreduce(n -> :(x.$n ≂ y.$n), (a,b)->:($a && $b), :(true), fieldnames(x))
    elseif (x <: Array) && (y <: Array)
        :(size(x) == size(y) && all(y .≂ x))
        :(x == y)

which of course still does not catch all cases. A properly implemented version could dispatch on type, and only use the generated function where it is need (composite types).

I find these things useful so I may package it up when I have time.


Awesome, I’ll use that for sure.

I 'll check this version later tonight. checked, it works for me!

and thank you.