March 19, 2013 Re: recursive equal, and firstDifference functions | ||||
---|---|---|---|---|
| ||||
Posted in reply to timotheecour | On Tuesday, March 19, 2013 19:13:14 timotheecour wrote:
> > And how do you even have the concept of recursion without some sort of range or container to recursively iterate through?
>
> It's not hard: I've done it for a serialization library I've been working on (which has some advantages over std.orange, such as serializing to json/binary etc. more on this later), as well as for my toStringRecurse: it works on any data type roughly as follows:
>
> auto equalRecurse(T)(T a) {
> static if isAssociativeArray!(T)) {
> }
> else static if(isArray!(T)) {
> }
> else static if(is(T == struct) ){
> foreach(i, member; a.tupleof) {
> ...
> }
> else static if...
> }
>
> >> Expecting the compiler to automagically figure out how to compare two types for you is just begging for trouble
>
> I beg to differ. I like to be as lazy as possible when writing user code (as opposed to library code). The compiler can do a lot of stuff automagically in D thanks to CT reflection. I didn't run into problems when using the serialization on rather complex nested objects.
Serialization is completely different from comparing the equality of two objects. That's opEquals' job. It deals with recursive comparisons like you're describing here just fine. There's no reason to define that externallly to the type. equal's job, on the other hand, is to compare the elements of two ranges - _not_ the ranges themselves - which is fundamentally different from ==.
- Jonathan M Davis
|
March 19, 2013 Re: recursive equal, and firstDifference functions | ||||
---|---|---|---|---|
| ||||
Posted in reply to Dan | > I think I understand what you are after and I like the idea. I've got a few: > -typesDeepEqual > -typesDeepCmp > -deepHash approxEqualRecurse could also be useful for numerical applications (same as equalRecurse, except uses approxEqual for floating point comparisons) > https://github.com/patefacio/d-help/blob/master/d-help/opmix/mix.d Thanks for the link, I'll take a look. |
March 19, 2013 Re: recursive equal, and firstDifference functions | ||||
---|---|---|---|---|
| ||||
Posted in reply to Jonathan M Davis | > That's opEquals' job. It deals with recursive comparisons like you're describing here just fine. Except the functionality is very different, as opEquals is blind to range semantics. I want equalRecurse to work as follows: struct A{ int[]b=[1]; } void main(){ auto a1=A(); auto a2=A(); assert(a1!=a2); //opEquals says it's different assert(equalRecurse(a1,a2)); //equalRecurse says it's the same // assert(equal(a1,a2)); //equal doesn't compile, only works for ranges. } > There's no reason to define that externallly to the type. Yes there is: it can be done automagically to work on vast majority of cases (see what several ppl have posted), and it avoids overly redundant boilerplate code. In my example here, I don't want to redefine an opEquals to make the "assert(a1==a2);" above pass, because: -it's boilerplate -equalRecurse has different semantics anyways -meaning of equalRecurse should be clear to anyone. |
March 19, 2013 Re: recursive equal, and firstDifference functions | ||||
---|---|---|---|---|
| ||||
Posted in reply to timotheecour | On Tuesday, March 19, 2013 21:13:21 timotheecour wrote:
> > That's opEquals' job. It deals with recursive comparisons like you're describing here just fine.
>
> Except the functionality is very different, as opEquals is blind to range semantics.
opEquals defines equality however it chooses to define equality. It's trivial for it to use equal on any member variables which are ranges. opEquals is the way that the language defines equality. equal is only necessary when trying to compare the elements of two ranges rather than compare two objects. == is what is supposed to be used to compare two objects. You seem to basically be trying to work your way around the language rather than using it the way it's designed, which is just asking for trouble.
And if the problem is that you don't want to write boilerplate code, and you want to define an opEquals that compares ranges with equal and everything else with ==, then it would be fairly trivial to write a string or template mixin to mix in opEquals for you. If you avoid defining == properly, then you're going to have problems wiith all kinds of stuff which assumes that == is used to define equality (including stuff like the built-in AAs). Avoiding boilerplate code is poor excuse to give your types odd semantics by not defining opEquals and using an external function to compare for equality.
- Jonathan M Davis
|
March 19, 2013 Re: recursive equal, and firstDifference functions | ||||
---|---|---|---|---|
| ||||
Posted in reply to Dan | On Tuesday, March 19, 2013 20:36:09 Dan wrote:
> On Tuesday, 19 March 2013 at 17:08:54 UTC, timotheecour wrote:
> > I think none of you got the point of equalRecurse, so let me clarify.
>
> I think I understand what you are after and I like the idea. I've got a few:
>
> -typesDeepEqual
> -typesDeepCmp
> -deepHash
Those are what opEquals, opCmp, and toHash are for. It might make sense to define mixins which implement them for you (dealing with whatever recursive semantics are necessary), but using external functions for those just isn't going to fly. The language and standard library are designed around them being part of the types themselves. Stuff like AAs won't work if those functions aren't defined on the types themselves.
- Jonathan M Davis
|
March 19, 2013 Re: recursive equal, and firstDifference functions | ||||
---|---|---|---|---|
| ||||
Posted in reply to Jonathan M Davis | On Tuesday, 19 March 2013 at 20:28:09 UTC, Jonathan M Davis wrote: > > Those are what opEquals, opCmp, and toHash are for. It might make sense to > define mixins which implement them for you (dealing with whatever recursive > semantics are necessary), but using external functions for those just isn't > going to fly. Understand the sentiment. But you can easily have those external functions call the member equivalents if they exist, and if not still proceed and work. This is what they do, in fact. For instance, it is nice to have the mixin for opCmp that actually calls member opCmp if they exist. This opens the door a bit. It allows you to have a reasonable opCmp for a type with a member that has none. The same approach can be used for dup/gdup - which we have discussed a few times now. > The language and standard library are designed around them being > part of the types themselves. Stuff like AAs won't work if those functions > aren't defined on the types themselves. > Sorry, I don't understand the last statement. |
March 19, 2013 Re: recursive equal, and firstDifference functions | ||||
---|---|---|---|---|
| ||||
Posted in reply to Dan | On Tuesday, March 19, 2013 21:50:55 Dan wrote:
> On Tuesday, 19 March 2013 at 20:28:09 UTC, Jonathan M Davis wrote:
> > The language and standard library are designed around them being
> > part of the types themselves. Stuff like AAs won't work if
> > those functions
> > aren't defined on the types themselves.
>
> Sorry, I don't understand the last statement.
Lots of stuff uses ==, toHash, etc. That's the way the language is designed. Defining your types without defining those properly just isn't going to work for a _lot_ of stuff. Creating an external function to compare objects or generate hashes or anything like that is just going to cause problems, because only your stuff would use it. The built-in stuff wouldn't, and the standard library wouldn't. For instance, AAs require that opEquals and toHash be defined, and no matter how clever your external functions for comparing objects or generating hashes are, they're not going to work with the built-in AAs. Any type which is going to work with the built-in AAs must define opEquals and toHash.
So, if the problem is boilerplate code, mixins can be used for opEquals, opCmp, etc. in order to define those for you, but creating external functions to do those same operations is not how the language was designed and isn't how anything is going to expect types to work. Anything that wants to be comparable, should define ==. Anything that wants to be hashable should define toHash. Etc. Something like equalRecurse ultimately really makes no sense at all.
- Jonathan M Davis
|
March 19, 2013 Re: recursive equal, and firstDifference functions | ||||
---|---|---|---|---|
| ||||
Posted in reply to Jonathan M Davis | On Tuesday, 19 March 2013 at 21:11:12 UTC, Jonathan M Davis wrote: > Lots of stuff uses ==, toHash, etc. That's the way the language is designed. Agreed > Defining your types without defining those properly just isn't going to work for > a _lot_ of stuff. Creating an external function to compare objects or generate > hashes or anything like that is just going to cause problems, because only > your stuff would use it. Ok - you are making strong statement and I don't see why it is the case. Here is an example that does work for hashing. Note: R implements its own hash and just prints to show nothing up the sleeve. Other classes (S,T) have no toHash implemented directly at all - yet you can see u1 and u3 resolve to the same hash and u2 a different, as you would expect. ------------------------- import std.stdio; import opmix.mix; import pprint.pp; struct R { string r; hash_t toHash() const nothrow { try { writeln("toHash called on r"); } catch(Exception) { } return typeid(string).getHash(&r); } } struct S { R r; string s; string[int] si; } struct T { S s; } struct U { mixin ToHash; T t; } void main() { U u1 = U(T(S(R("a"), "foo", [1:"goo"]))); U u2 = U(T(S(R("a"), "foo".idup))); U u3 = U(T(S(R("a"), "foo".idup, [1:"goo".idup]))); writeln(deepHash(u1)); writeln(deepHash(u2)); writeln(deepHash(u3)); writeln(pp(u1)); int[U] aa; aa[u1] = 100; writeln("Is u1 in aa ", u1 in aa); writeln("Is u2 in aa ", u2 in aa); aa[u2] = 100; writeln("Is u2 in now aa ", u2 in aa); } ----------------------- OUTPUT -------------------------- toHash called on r 614624 toHash called on r 582446 toHash called on r 614624 { (U).t = { (T).s = { (S).r = { (R).r = "a" } (S).s = "foo" (S).si = { (K(1)[0] => V("goo")), } } } } toHash called on r toHash called on r Is u1 in aa 7F2C7E556FC0 toHash called on r Is u2 in aa null toHash called on r toHash called on r Is u2 in now aa 7F2C7E556F40 ---------------------------------------------------------------------- > The built-in stuff wouldn't, and the standard library > wouldn't. For instance, AAs require that opEquals and toHash be defined, and no > matter how clever your external functions for comparing objects or generating > hashes are, they're not going to work with the built-in AAs. Any type which is > going to work with the built-in AAs must define opEquals and toHash. > The above works with the built-in AAs. Please offer an example. Thanks Dan |
March 19, 2013 Re: recursive equal, and firstDifference functions | ||||
---|---|---|---|---|
| ||||
Posted in reply to Dan | On Tuesday, March 19, 2013 22:43:10 Dan wrote:
> The above works with the built-in AAs.
> Please offer an example.
It works because the outer type defines toHash. Without toHash, the built-in AAs won't work. If you're dealing with member variables which don't have toHash, then doing something like you did is an option, but the outer type still needs toHash, and if any of the member variables define an opEquals but not a toHash, then you risk having your hash change when a member variable changes even when the new value of the member variable is considered equal to the old one (e.g. because a cached value or some other member variable in that type is not considered to be part of the equality of an object but _would_ be considered to be part of the hash if you use reflection to generate a hash from all of that type's member variables). So, using reflection to generate hashes for arbitrary types can be risky. Whether it works on not depends on how those types are defined and what you're doing with them.
But the main problem that I'm pointing out is that you can't define your own, non-standard functions for equality or hashing or whatever and expect your types to play nicely with other stuff. If your stuff is wrapped in types that do define the proper functions for that (like in your example), then it can work, but the types which were wrapped won't play nice outside of the wrapper.
- Jonathan M Davis
|
March 20, 2013 Re: recursive equal, and firstDifference functions | ||||
---|---|---|---|---|
| ||||
Posted in reply to Jonathan M Davis | On Tuesday, 19 March 2013 at 23:13:19 UTC, Jonathan M Davis wrote: > On Tuesday, March 19, 2013 22:43:10 Dan wrote: >> The above works with the built-in AAs. >> Please offer an example. > > It works because the outer type defines toHash. Without toHash, the built-in > AAs won't work. If you're dealing with member variables which don't have > toHash, then doing something like you did is an option, but the outer type > still needs toHash, and if any of the member variables define an opEquals but > not a toHash, then you risk having your hash change when a member variable > changes even when the new value of the member variable is considered equal to > the old one (e.g. because a cached value or some other member variable in that > type is not considered to be part of the equality of an object but _would_ be > considered to be part of the hash if you use reflection to generate a hash from > all of that type's member variables). So, using reflection to generate hashes > for arbitrary types can be risky. Whether it works on not depends on how those > types are defined and what you're doing with them. > For hashing it is not foolproof as you mentioned. It is important to understand that opEquals and toHash should share the same semantics (i.e. if they are equal they should hash the same). But, in general, when you control the code you don't need toHash or opEquals at every level of composition and it will still work fine (compile-time recursively) as a key in a AA. > But the main problem that I'm pointing out is that you can't define your own, > non-standard functions for equality or hashing or whatever and expect your > types to play nicely with other stuff. If your stuff is wrapped in types that do > define the proper functions for that (like in your example), then it can work, > but the types which were wrapped won't play nice outside of the wrapper. > This is true, but then my code is by definition not standard. However, theoretically, the language writers could. For example, any '==' could be lowered to a 'standard' function, probably better named 'intancesDeepEqual(a,b)' and that function could use reflection to provide equal when not available else call opEquals if it is available. Similar with opCmp, dup, idup, ... In other words, in the vein of the original poster, why not allow all of these nice goodies (equality comparison, opCmp comparison, dup) without requiring boilerplate code while still honoring/using it when it is provided. Thanks Dan |
Copyright © 1999-2021 by the D Language Foundation