ANN: WhyNotEqual.jl

Ever encountered a situation like this:

@test expected == result
Test Failed at /home/jan/.julia/dev/WhyNotEqual/doit.jl:21
  Expression: expected == result
   Evaluated: (v = (hello = :world, language = :julia), w = 42, x = [1, 2, 3, 4, 5], y = AB(1, 2), 
z = Dict{AB, Any}(AB(2, 3) => AB(3, ()), AB(1, 2) => 3), zz = (foo = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10
  …  91, 92, 93, 94, 95, 96, 97, 98, 99, 100], bar = :bar)) == (v = (hello = :world, language = :ju
lia), w = 42, x = [1, 2, 3, 4, 5], y = AB(1, 2), z = Dict{AB, Any}(AB(2, 3) => AB(4, ()), AB(1, 2) 
=> 3), zz = (foo = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10  …  91, 92, 93, 94, 95, 96, 97, 98, 99, 100], bar
 = :bar))
ERROR: LoadError: There was an error during testing

Can you spot the difference? This WhyNotEqual.jl can do it for you:

using WhyNotEqual
whynot(==, expected, result)
DifferentAndNoChildren: When applying `lens` to both objects, we get `obj1` and `obj2`.
obj1 and obj2 are different, but they don't have any children.
lens: (@optic _.z[AB(2, 3)].a)
obj1: 3
obj2: 4
24 Likes

A beautiful application of the amazing Accessors.jl API!

5 Likes

I really like the idea, thanks for creating this! But I wonder if the output could be more useful: does it really provide that much more information than the fact that the two aren’t equal? Sure, I can see that 3 is different from 4, but in many cases I may not be able to guess where that 3 or 4 came from. Scanning those two objects, there are a lot of 3s and 4s, which “slot” holds the difference?

What about using something like AbstractTrees to provide context? Or maybe even FoldingTrees to make it interactive?

8 Likes

Very cool. Could it also do other comparisons, like <?

I wonder if a macro could do something like this:

>> @whynot expected == result

expected.z[AB(2, 3)].a: 3
result.z[AB(2, 3)].a: 4

?

3 Likes

So interesting line is this:

lens: (@optic _.z[AB(2, 3)].a)

Sure I am open to make it interactive or make the message more friendly.

3 Likes

Ah, good. Might be worth explaining, it’s very mysterious to someone who has never looked at Accessors.jl before. Not exactly sure how to do that, though, without digging in to that package.

3 Likes

Yeah, that is probably are more friendly error message.

Perhaps some inspiration for the display of the diff can be had from GitHub - ssfrr/DeepDiffs.jl: Compute and pretty-print diffs for data structures like arrays and dictionaries ?

1 Like

Very cool. Could it also do other comparisons, like <?

Instead of whynot(==, obj1, obj2) you can do whynot(cmp, obj1, obj2) for any comparison that takes two objects and returns a bool. This will generally work well for ==, ===, isequal or anything else, that satisfies the same structural recursion rules. For instance cmp of two arrays should imply cmp for each element. Or cmp for two objects should imply cmp for all of their properties etc.

2 Likes

Looks really neat!
Any way to list all mismatches, not only the first one?

Currently not, but I don’t see significant technical obstacles to doing this.