www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - "==" not symmetric

reply "Lionello Lunesu" <lionello lunesu.remove.com> writes:
I was checking some sources in the 'internal' folder and saw this:

#int _d_obj_eq(Object o1, Object o2)
#{
#    return o1 is o2 || (o1 && o1.opEquals(o2));
#}

truth table:
o1 o2   result
 0   0     1
 0   x     0
 x   0     opEquals, crash?
 x   x     1
 y   x     opEquals

It's interesting that (o1==o2) is not necessarily the same as (o2==o1)!
Since o1=x,o2=0 returns 0, the other way around should return 0 as well.

I suppose the function should be also check o2, resulting in:

#int _d_obj_eq(Object o1, Object o2)
#{
#    return o1 is o2 || (o1 && o2 && o1.opEquals(o2));
#}

L. 
May 23 2006
parent reply "Unknown W. Brackets" <unknown simplemachines.org> writes:
Forgive my rusty logic skills... how do I read your table?  As far as I 
see it:

o1     o2     result
null   null   true
null   valid  false
null   bad    false
valid  null   result of opEquals(null) - probably false
valid  valid  result of opEquals(valid) - unknown result
valid  bad    result of opEquals(bad) - probably false
bad    null   segfault
bad    valid  segfault
bad    bad    segfault unless equal bad pointers, then true

I don't know what "y" is on your table.

Of course, you could throw that off with an opEquals like this:

bool opEquals(Object o)
{
    return true;
}

Obviously, we have to assume that o1 and o2 are of the same type, or 
else different opEquals might be called.  So, okay... the only 
differences we have:

If o1 is null, reversing the comparison will depend on opEquals 
returning false when it is passed a null pointer (which should be the case.)

Comparing a valid pointer to a bad pointer cannot be properly reversed, 
or it might cause a segfault.  This is always wrong anyway imho.

That said, opEquals (imho) will either always check for null or cannot 
be reversed (null == evaluatesToNullObject is always false.)  So it 
would seem logical to do something like you suggested.

-[Unknown]


 truth table:
 o1 o2   result
  0   0     1
  0   x     0
  x   0     opEquals, crash?
  x   x     1
  y   x     opEquals
 
 It's interesting that (o1==o2) is not necessarily the same as (o2==o1)!
 Since o1=x,o2=0 returns 0, the other way around should return 0 as well.
 
 I suppose the function should be also check o2, resulting in:
 
 #int _d_obj_eq(Object o1, Object o2)
 #{
 #    return o1 is o2 || (o1 && o2 && o1.opEquals(o2));
 #}

May 23 2006
parent reply "Lionello Lunesu" <lionello lunesu.remove.com> writes:
"Unknown W. Brackets" <unknown simplemachines.org> wrote in message 
news:e50lr3$rqo$1 digitaldaemon.com...
 o1     o2     result
 null   null   true
 null   valid  false
 null   bad    false
 valid  null   result of opEquals(null) - probably false
 valid  valid  result of opEquals(valid) - unknown result
 valid  bad    result of opEquals(bad) - probably false
 bad    null   segfault
 bad    valid  segfault
 bad    bad    segfault unless equal bad pointers, then true

'bad'? I don't think you can tell whether a pointer is bad or valid. I think calling opEquals(null) is bad, since it's likely to crash. And I've noticed before that many implementations start with a if(lhs is null) check. It just makes sense to put that check 1 function up in the code, namely in the ==.
 I don't know what "y" is on your table.

'y' is just different from 'x' :)
 bool opEquals(Object o)
 {
    return true;
 }

 Obviously, we have to assume that o1 and o2 are of the same type, or else 
 different opEquals might be called.

Might not be a bad idea to also check the classinfo of both objects in the == ?
 So, okay... the only differences we have:

 If o1 is null, reversing the comparison will depend on opEquals returning 
 false when it is passed a null pointer (which should be the case.)

Right. At the moment opEquals _must_ begin with "if (lhs is null) return false;", but this can easily be enforced by putting the same check in ==.
 Comparing a valid pointer to a bad pointer cannot be properly reversed, or 
 it might cause a segfault.  This is always wrong anyway imho.

Right. Nothing you can do about that I'm afraid.
 That said, opEquals (imho) will either always check for null or cannot be 
 reversed (null == evaluatesToNullObject is always false.)  So it would 
 seem logical to do something like you suggested.

It would seem so :) L.
May 23 2006
parent reply "Unknown W. Brackets" <unknown simplemachines.org> writes:
Bad means a pointer to an invalid object.  A bad pointer.  I was trying 
to cover all the bases.

Your typical opEquals looks like this:

bool opEquals(Object o)
{
    Foo f = cast(Foo) o;

    // If o was null, or if f is now null (doesn't match class) fail.
    if (f is null)
       return false;

    return f.prop == this.prop;
}

Anything else either doesn't work properly with subclassing, isn't 
typesafe, or will segfault.  This doesn't seem wrong to me.

Okay, missed that x and y were supposed to be objects.  You seem to be 
missing a "x y" case, then.  This would be the same as "y x", though.

It would be a bad idea to check the class info imho.  Why can't a XYZ 
equal an ABC even if they share no parents aside from Object?  But that 
does mean the order of the comparison does matter (consider Array == 
SpecializedArray vs. SpecializedArray == Array.)

No, opEquals should never begin with any check on lhs... lhs will be 
this.  It should however check rhs, first casting it more than likely, 
and then checking for null on the cast.... since, if the cast fails, it 
will be null.

This same check cannot be necesarily made in ==, since it can't know 
what other classes the opEquals supports.

Still, checking for null could be a good thing for sanity; I'd guess it 
doesn't because opEquals would have to anyway.

-[Unknown]


 'bad'? I don't think you can tell whether a pointer is bad or valid. I think 
 calling opEquals(null) is bad, since it's likely to crash. And I've noticed 
 before that many implementations start with a if(lhs is null) check. It just 
 makes sense to put that check 1 function up in the code, namely in the ==.
 
 I don't know what "y" is on your table.

'y' is just different from 'x' :) Might not be a bad idea to also check the classinfo of both objects in the == ?
 So, okay... the only differences we have:

 If o1 is null, reversing the comparison will depend on opEquals returning 
 false when it is passed a null pointer (which should be the case.)

Right. At the moment opEquals _must_ begin with "if (lhs is null) return false;", but this can easily be enforced by putting the same check in ==.
 Comparing a valid pointer to a bad pointer cannot be properly reversed, or 
 it might cause a segfault.  This is always wrong anyway imho.

Right. Nothing you can do about that I'm afraid.
 That said, opEquals (imho) will either always check for null or cannot be 
 reversed (null == evaluatesToNullObject is always false.)  So it would 
 seem logical to do something like you suggested.

It would seem so :)

May 23 2006
parent Lionello Lunesu <lio lunesu.remove.com> writes:
Going thirty and I'm still mixing left and right :S

You've made some good points. Indeed, since opEquals has to check the 
type of the passed Object, it'll automatically check for null too.

Still odd though, that you can get a different result when you do o1==o2 
or o2==o1, but I guess the library programmer should take care of that :)

L.

Unknown W. Brackets wrote:
 Bad means a pointer to an invalid object.  A bad pointer.  I was trying 
 to cover all the bases.
 
 Your typical opEquals looks like this:
 
 bool opEquals(Object o)
 {
    Foo f = cast(Foo) o;
 
    // If o was null, or if f is now null (doesn't match class) fail.
    if (f is null)
       return false;
 
    return f.prop == this.prop;
 }
 
 Anything else either doesn't work properly with subclassing, isn't 
 typesafe, or will segfault.  This doesn't seem wrong to me.
 
 Okay, missed that x and y were supposed to be objects.  You seem to be 
 missing a "x y" case, then.  This would be the same as "y x", though.
 
 It would be a bad idea to check the class info imho.  Why can't a XYZ 
 equal an ABC even if they share no parents aside from Object?  But that 
 does mean the order of the comparison does matter (consider Array == 
 SpecializedArray vs. SpecializedArray == Array.)
 
 No, opEquals should never begin with any check on lhs... lhs will be 
 this.  It should however check rhs, first casting it more than likely, 
 and then checking for null on the cast.... since, if the cast fails, it 
 will be null.
 
 This same check cannot be necesarily made in ==, since it can't know 
 what other classes the opEquals supports.
 
 Still, checking for null could be a good thing for sanity; I'd guess it 
 doesn't because opEquals would have to anyway.
 
 -[Unknown]
 
 
 'bad'? I don't think you can tell whether a pointer is bad or valid. I 
 think calling opEquals(null) is bad, since it's likely to crash. And 
 I've noticed before that many implementations start with a if(lhs is 
 null) check. It just makes sense to put that check 1 function up in 
 the code, namely in the ==.

 I don't know what "y" is on your table.

'y' is just different from 'x' :) Might not be a bad idea to also check the classinfo of both objects in the == ?
 So, okay... the only differences we have:

 If o1 is null, reversing the comparison will depend on opEquals 
 returning false when it is passed a null pointer (which should be the 
 case.)

Right. At the moment opEquals _must_ begin with "if (lhs is null) return false;", but this can easily be enforced by putting the same check in ==.
 Comparing a valid pointer to a bad pointer cannot be properly 
 reversed, or it might cause a segfault.  This is always wrong anyway 
 imho.

Right. Nothing you can do about that I'm afraid.
 That said, opEquals (imho) will either always check for null or 
 cannot be reversed (null == evaluatesToNullObject is always false.)  
 So it would seem logical to do something like you suggested.

It would seem so :)


May 24 2006