Comments (9)
Properly supporting cyclic data structures is very challenging. Even though I added that TODO, over time I've become convinced that cmp
shouldn't support cyclic structures by default in order to avoid further complicating its logic.
I'm aware that reflect.DeepEqual
supports cyclic structures, but their definition of equality is strange and probably not what the user expects. Consider this example. The graphs have clearly different layouts, yet they are all considered the same.
I should note that some cyclic structures are only cyclic because the structure is conceptually a tree, but every node has a "weak" reference to the root. In those situations, you can use an Ignore
option to prevent cmp
from traversing back to the root.
If someone has a good design for supporting graph equality (and fits in well with the ecosystem of cmp.Option
), I'll consider it. For now, I have no intention of adding this functionality.
from go-cmp.
We could save only the curPath.String() for this purpose I guess?
That's still approximately the same amount of work (since you need to do string formatting). Furthermore, the string is not sufficient for avoid all cases of ambiguity.
Also we can make this optional with an Option?
I'm open to that possibility.
Another option is not to save the path, have similar "weird" behavior of DeepEqual, but at least not get into an infinite recursion as the current behavior is.
I've always been bothered by the DeepEqual
behavior and believe it would be a mistake to replicate it.
from go-cmp.
Thanks for the detailed explanation! 👍
What about this sort of solution?
Add a memory
variable to the state
struct, that will be a set of a pair of addresses: those addresses are addresses of variables that their comparisons was already started.
We can add the addresses to this set in the compareAny
switch, in reflect.Struct
case.
Then, in the compareAny
switch, reflect.Ptr
case, we can add a test that if the pointers point to addresses that are already in the set, we skip the recursive call.
from go-cmp.
In my opinion, it is not sufficient to simply just check whether we have visited a pointer before. If you read the rules of reflect.DeepEqual
, that is exactly what they do:
As DeepEqual traverses the data values it may find a cycle. The second and subsequent times that DeepEqual compares two pointer values that have been compared before, it treats the values as equal rather than examining the values to which they point.
This approach is why reflect.DeepEqual
has strange results where graphs of clearly different structures are considered equal.
I think you would need to do something like this:
- After descending into a pointer, record the current
cmp.Path
and the pointer address. - Thus, when you encounter a pointer, check whether any pointer was already visited:
- If neither the left nor right object had ever visited any of the pointers, recursively descend into both of them.
- If only one side had visited the pointer previously, but not the other. Then, report unequal.
- (both sides have previously visited pointers)
- Report equal if the Paths associated with those pointers are equal.
- Otherwise, report unequal because the Paths differ.
However, I don't know how to implement that efficiently. There's also an open question of what it even means for two Paths to be equal.
from go-cmp.
@dsnet sounds good,
What do you mean by "efficiently" what's inefficient in this?
from go-cmp.
About the tag of this issue, I think this is a bug, not enhancement.
from go-cmp.
What do you mean by "efficiently" what's inefficient in this?
The current implementation of cmp.Equal
only has a single field for curPath
. The Path
is treated a stack where PathSteps
are pushed to and popped from it.
Adding the logic described above (assuming it is correct; which I'm not convinced of) requires that you copy the entire Path
at ever pointer indirection (since the original state.curPath
field will continue to be mutated). I suspect that is going to be expensive.
About the tag of this issue, I think this is a bug, not enhancement.
I guess that's a matter of perspective. I'm going to keep the label as is.
from go-cmp.
We could save only the curPath.String()
for this purpose I guess? Also we can make this optional with an Option?
Another option is not to save the path, have similar "weird" behavior of DeepEqual
, but at least not get into an infinite recursion as the current behavior is.
from go-cmp.
Yay! Cheers!
from go-cmp.
Related Issues (20)
- Diff is extremely slow HOT 3
- How to ignore specific field by name? HOT 1
- Ideas for a v2 HOT 3
- Default support for the `netip` package HOT 1
- New minor release of the library? HOT 4
- cmp.Diff() switching from property diffs to "new object" diff when object changes significantly HOT 1
- Inconsistent whitespace produced by cmp.Diff HOT 2
- cmpopts: add EquateUnsetFields(want) or FilterFieldsFrom(want) HOT 1
- IgnoreFields and nested struct HOT 2
- Enable a dependency update tool HOT 2
- Package Name Mismatches HOT 4
- unhelpful output for difference in slices HOT 1
- How to implement jests.js's `expect.any(String)` like functionality? HOT 1
- cmpopts.IgnoreFields does not work on structs with an Equal method HOT 1
- Performance Issue Comparing Array Types HOT 1
- Naming clash for new cmp standard package HOT 4
- Comparing two fields with diffirent types that are semantically equivalent HOT 3
- String diff for UUIDs can use strings.Join syntax which makes it more confusing HOT 4
- Reporter truncating important context on diff output but leaving others alone
- How to add more context to Diff output?
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from go-cmp.