Summary:
Add one more DeepEqual function (proposed name: DeepEqualWithPredicates) that has one more argument predicates map[string]func(interface{}, interface{}) bool. Predicates is map from type name to compare method for this type. During recursive value traversal if value has type existing in the predicates map then user-defined comparator is used instead of just ==.
Motivation:
There are many cases when you need to compare deep objects but often you cannot use out of the box reflect.DeepEqual. DeepEqual doesn't allow you to compare float numbers with specific precision (with fixed or relative margin) and doesn't allow to skip some mutable fields of structs that are not represent the real state of the object. There already were some proposals to fix at least the second problem (issues/20444), but current proposal solution is more general and covers both cases with float comparisons and class comparison. Behavior of DeepEqual will not be broken by this change (it will reuse DeepEqualWithPredicates with nil predicates map what will lead to the same behavior as without proposed change).
Details:
What should be done to make this change:
type PredicateMap map[string]func(interface{}, interface{}) boolfunc DeepEqualWithPredicates(x, y interface{}, predicates PredicateMap) bool {deepValueEqual to get one more argument predicates PredicateMap and use it for types that in mapDeepEqual to reuse DeepEqualWithPredicates as DeepEqual became particular case of DeepEqualWithPredicates (with predicates = nil)Opens:
If you will be ok with this proposal I need some your advices:
v.Type().Name()), but probably it should be some another type representation, as in case of Pointer v.Type().Name() is empty (workaround: "*" + v.Elem().Type().Name())Have you had a look at third-party libraries like https://github.com/google/go-cmp?
Yes, I have. In particular this library is really powerful, it is great. But on my mind it would be good to have some abilities to extend DeepEqual functionality out of the box. It covers many use cases without any dependencies to third-party.
Thank you, I understand it. Ok, just want to ensure that you read the proposal. For me it doesn't look as big change that will cost a lot of maintenance. This just provide user more power in usage of functionality that already in go std lib.
and provide key functionality that many Go programs require
Deep equal comparison is really key functionality, but in most cases it is not correct to compare floats on just "==". So why deep equal with predicates cannot be considered as key functionality? Lack of this functionality in std lib cause many third party implementations.
Note that the cost in adding new APIs isn't only the implementation and maintenance; we're also adding more complexity for Go developers to learn. Adding new APIs, particularly tricky ones like this one, isn't easy.
For example, there have been similar proposals in the past, which weren't accepted: https://github.com/golang/go/issues/20444
I think a proposal to change DeepEqual or add a more powerful version of it needs to be really well thought out. For example:
DeepEqual?Equal methods?Stepping back a bit, I've always found it somewhat odd that DeepEqual was in the reflect package in the first place. The purpose of reflect is to provide a programmatic way to interact with arbitrary Go types and is supposed to provide a dynamic API over the functionality that the Go language allows you to do statically.
DeepEqual doesn't quite fit that purpose. While it obviously uses reflect under the hood for its implementation, the language functionality that it most closely models is that of the == operator in the Go language. However, it's behavior is neither identical (since it descends into pointers) nor a superset of the == operator (since it is extended to work on slices and maps).
As such, I'm not sure extending the functionality of DeepEqual is good idea, especially in the reflect package. As pointed out by @mvdan, what does reflect.DeepEqualWithPredicates provide that the cmp package doesn't?
What experience do you have with third party alternatives? Why are they not enough?
Actually I do not see any problem to use third-party. Just have observed that some functionality that already in std lib can be extended.
What rough percentage of users needing to do deep comparisons require more than just DeepEqual?
Tough question.. As I already told there are two cases:
In my practice there were 2 of 3 cases when I cannot use DeepEqual (in our tests actually not in product code) and it was one with floats and one with struct. And one case when I successfully used DeepEqual with data that fits for it.
Why is this API (with a map parameter) better than other designs, like go-cmp's with Equal methods?
As pointed out by @mvdan, what does reflect.DeepEqualWithPredicates provide that the cmp package doesn't?
It provides the same that go-cmp but out of the box.
In general, I agree with you that there is no strong need for this change. And if you suspect that this will introduce additional code complexity and difficulties for users, let's close this issue. Thank you for considering this.
Thanks.
Most helpful comment
Stepping back a bit, I've always found it somewhat odd that
DeepEqualwas in thereflectpackage in the first place. The purpose ofreflectis to provide a programmatic way to interact with arbitrary Go types and is supposed to provide a dynamic API over the functionality that the Go language allows you to do statically.DeepEqualdoesn't quite fit that purpose. While it obviously usesreflectunder the hood for its implementation, the language functionality that it most closely models is that of the==operator in the Go language. However, it's behavior is neither identical (since it descends into pointers) nor a superset of the==operator (since it is extended to work on slices and maps).As such, I'm not sure extending the functionality of
DeepEqualis good idea, especially in thereflectpackage. As pointed out by @mvdan, what doesreflect.DeepEqualWithPredicatesprovide that thecmppackage doesn't?