Understood.
However, I have to strongly disagree with you - or is it Walter? - that the D comparison idiom is to eschew direct use of == and === and go for something like your equals(Object left, Object right).
This seems to be the worst of all possible worlds. D's bitten by its own cleverness here. Consider:
Java sucks because == works differently for fundamental and object types, necessitating the inconsistency of using equals() for object types.
C# attempts to address this with operator overloading, but it still leaves one clueless as to whether any reference comparisons are comparing equivalence or identity. This also sucks, unless one "trusts" the author of a class. (Got to love that trust ... it never let's you down, eh?)
C++ is entirely straightforward in that == on pointers always checks identity and on references/instances always checks equivalence. Notwithstanding this entirely reliable consistency, it is a source of confusion to novices simply because pointers and references are difficult concepts to absorb and disambiguate.
D sensibly addresses this confusing issue once and for all by separating equivalence and identity into separate operators, and making them syntatically consistent, hence my comment of satisfaction in the "Identity & equivalence" thread. However, this opportunity for clarity seems to have been thrown if what many of you are telling me is correct, which is that
class X
{
}
X y = . . .;
void f(X x)
{
if(x == y)
will cause an access violation if x or y are null. It makes a nonsense of the attempt to deal with the issue. If this cannot be fixed with == (and ===) syntax, then we're probably better off removing these operators from the language and forcing use of Object.equals() and Object.equalsRef(). For my part, though, that'd be gross. So:
It seems that we must find a way to sensibly incorporate === in ==. We could engineer the compiler, or mandate the class author, to incorporate identity checks in equivalence comparisons. However, there are circumstances in which the call to === would be superfluous, reducing efficiency.
afaics, we need to cater for the following scenarios
if(x == y)
(i) x and y are non-null
(ii) x is null
(iii) y is null
(iv) x and y are null
the identity comparison _must_ be involved in == for (ii) and (iv), and
probably for (iii). I suggest that classes have two comparison functions,
both static, called eq() and eqi().
eq() compares two references for equality. It may assume that both references are guaranteed non-null. If eq() is not supported by a class, the compiler provides "a bit compare of the contents of the two structs is done to determine equality or inequality" as per the current fashion.
eqi() checks for references for equality, mindful of identity. It checks
whether either or both of the references are null, and if they are not it
returns the result of calling eq() on them. The default eqi() implementation
would be
class X
{
static int eqi(X lhs, X rhs)
{
if(lhs === rhs) // Identity comparison
{
return true; // Same instance, therefore equal
}
else if(lhs === null &&
rhs === null)
{
0; // One, but not both (because of check above), is null, so not
equal
}
else
{
return X.eq(lhs, rhs); // Both non-null, do equivalence comparison
}
The author of a class can provide
- neither
- eq() only - the usual case
- eqi() only
- eq() and eqi()
(I can't imagine a reason for either of the last two, but one never knows
...)
It would be *entirely* up to the compiler as to whether it translates x == y
as a call to X.eqi(x, y) or to X.eqi(x, y), presumably based on what it
knows about the context of the two references. This means that any
user-defined implementations of eq() and eqi() should be written with this
in mind, presumably meaning that eqi() would do anything other than the
default as shown above, apart from logging or other benign things.
Given static eq() and static eqi() we would no longer have non-static eq()
as a special function, and could likely get rid of it. (I haven't thought
about that part yet.)
Sound ok?
Comments appreciated. I certainly can't see how we can have access violations when using == in expressions!!
Matthew
"Daniel Yokomiso" <daniel_yokomiso@yahoo.com.br> wrote in message news:b5s1hg$ca1$1@digitaldaemon.com...> "Matthew Wilson" <dmd@synesis.com.au> escreveu na mensagem news:b5r9l2$2r2u$1@digitaldaemon.com...> > In the documentation it says that the expression (a == b) is rewritten
as
> > a.equals(b).
> >> > Given that, what happens if a is null? NullPointerException, or is there
a
> > "safe-this" mechanism, ie. a.this is automatically checked against null?
> If
> > not automatic, is it possible to write something such as
> >> > class X
> > {
> > int value;
> >> > int eq(X rhs)
> > {
> > if(this === rhs)
> > {
> > return 1;
> > }
> > else if(rhs === null)
> ^^^^^^ -> lhs
>> > {
> > return false;
> > }
> > else if(rhs === null)
> > {
> > return false;
> > }
> > else
> > {
> > return this.value == rhs.value;
> > }
> > }
> > }
> >> > (I expect I've got some syntax, wrong, but presumably you know what I
> mean.)
> >> > This implementation then supports
> >> > X x1 = new X(1);
> > X x2 = new X(3);
> > X x3 = null;
> >> > if(x1 == x2) // (i)
> > {}
> > if(x1 == x3) // (ii)
> > {}
> > if(x3 == x2) // (iii)
> > {}
> >> > If all this is not supported, what would be the result of (iii) - crash?
> >>> Access violation. It's safer to use a generic equals function that will do the appropriate checks:
>>> equals(Object left, Object right) {
> if (left === right) {
> return true;
> } else if ((left === null) || (right === null)) {
> return false;
> } else {
> return left == right;
> }
> }
>>> If (iii) was valid we could never be certain to use "this" reference
inside
> methods, because it could be null. But the invariant of Object is
> "assert(this !== null);".
>>> ---
> Outgoing mail is certified Virus Free.
> Checked by AVG anti-virus system (http://www.grisoft.com).
> Version: 6.0.463 / Virus Database: 262 - Release Date: 17/3/2003
>>

Matthew Wilson wrote:
> Understood.
> > However, I have to strongly disagree with you - or is it Walter? - that the
> D comparison idiom is to eschew direct use of == and === and go for
> something like your equals(Object left, Object right).
> > This seems to be the worst of all possible worlds. D's bitten by its own
> cleverness here. Consider:
> > Java sucks because == works differently for fundamental and object types,
> necessitating the inconsistency of using equals() for object types.
> > C# attempts to address this with operator overloading, but it still leaves
> one clueless as to whether any reference comparisons are comparing
> equivalence or identity. This also sucks, unless one "trusts" the author of
> a class. (Got to love that trust ... it never let's you down, eh?)
> > C++ is entirely straightforward in that == on pointers always checks
> identity and on references/instances always checks equivalence.
> Notwithstanding this entirely reliable consistency, it is a source of
> confusion to novices simply because pointers and references are difficult
> concepts to absorb and disambiguate.
> > D sensibly addresses this confusing issue once and for all by separating
> equivalence and identity into separate operators, and making them
> syntatically consistent, hence my comment of satisfaction in the "Identity &
> equivalence" thread. However, this opportunity for clarity seems to have
> been thrown if what many of you are telling me is correct, which is that
> > class X
> {
> }
> > X y = . . .;
> > void f(X x)
> {
> if(x == y)
> > will cause an access violation if x or y are null. It makes a nonsense of
> the attempt to deal with the issue. If this cannot be fixed with == (and
> ===) syntax, then we're probably better off removing these operators from
> the language and forcing use of Object.equals() and Object.equalsRef(). For
> my part, though, that'd be gross. So:
> > It seems that we must find a way to sensibly incorporate === in ==. We could
> engineer the compiler, or mandate the class author, to incorporate identity
> checks in equivalence comparisons. However, there are circumstances in which
> the call to === would be superfluous, reducing efficiency.
> > afaics, we need to cater for the following scenarios
> > if(x == y)
> > (i) x and y are non-null
> (ii) x is null
> (iii) y is null
> (iv) x and y are null
> > the identity comparison _must_ be involved in == for (ii) and (iv), and
> probably for (iii). I suggest that classes have two comparison functions,
> both static, called eq() and eqi().
> > eq() compares two references for equality. It may assume that both
> references are guaranteed non-null. If eq() is not supported by a class, the
> compiler provides "a bit compare of the contents of the two structs is done
> to determine equality or inequality" as per the current fashion.
> > eqi() checks for references for equality, mindful of identity. It checks
> whether either or both of the references are null, and if they are not it
> returns the result of calling eq() on them. The default eqi() implementation
> would be
> > class X
> {
> static int eqi(X lhs, X rhs)
> {
> if(lhs === rhs) // Identity comparison
> {
> return true; // Same instance, therefore equal
> }
> else if(lhs === null &&
> rhs === null)
> {
> 0; // One, but not both (because of check above), is null, so not
> equal
> }
> else
> {
> return X.eq(lhs, rhs); // Both non-null, do equivalence comparison
> }
> > The author of a class can provide
> > - neither
> - eq() only - the usual case
> - eqi() only
> - eq() and eqi()
> > (I can't imagine a reason for either of the last two, but one never knows
> ...)
> > It would be *entirely* up to the compiler as to whether it translates x == y
> as a call to X.eqi(x, y) or to X.eqi(x, y), presumably based on what it
> knows about the context of the two references. This means that any
> user-defined implementations of eq() and eqi() should be written with this
> in mind, presumably meaning that eqi() would do anything other than the
> default as shown above, apart from logging or other benign things.
> > Given static eq() and static eqi() we would no longer have non-static eq()
> as a special function, and could likely get rid of it. (I haven't thought
> about that part yet.)
> > Sound ok?
> > Comments appreciated. I certainly can't see how we can have access
> violations when using == in expressions!!
> > Matthew
> > > "Daniel Yokomiso" <daniel_yokomiso@yahoo.com.br> wrote in message
> news:b5s1hg$ca1$1@digitaldaemon.com...> >>"Matthew Wilson" <dmd@synesis.com.au> escreveu na mensagem
>>news:b5r9l2$2r2u$1@digitaldaemon.com...>>>>>In the documentation it says that the expression (a == b) is rewritten
> > as
> >>>a.equals(b).
>>>>>>Given that, what happens if a is null? NullPointerException, or is there
> > a
> >>>"safe-this" mechanism, ie. a.this is automatically checked against null?
>>>>If
>>>>>not automatic, is it possible to write something such as
>>>>>>class X
>>>{
>>> int value;
>>>>>> int eq(X rhs)
>>> {
>>> if(this === rhs)
>>> {
>>> return 1;
>>> }
>>> else if(rhs === null)
>>>> ^^^^^^ -> lhs
>>>>>>> {
>>> return false;
>>> }
>>> else if(rhs === null)
>>> {
>>> return false;
>>> }
>>> else
>>> {
>>> return this.value == rhs.value;
>>> }
>>> }
>>>}
>>>>>>(I expect I've got some syntax, wrong, but presumably you know what I
>>>>mean.)
>>>>>This implementation then supports
>>>>>>X x1 = new X(1);
>>>X x2 = new X(3);
>>>X x3 = null;
>>>>>>if(x1 == x2) // (i)
>>>{}
>>>if(x1 == x3) // (ii)
>>>{}
>>>if(x3 == x2) // (iii)
>>>{}
>>>>>>If all this is not supported, what would be the result of (iii) - crash?
>>>>>>>Access violation. It's safer to use a generic equals function that will do
>>the appropriate checks:
>>>>>>equals(Object left, Object right) {
>> if (left === right) {
>> return true;
>> } else if ((left === null) || (right === null)) {
>> return false;
>> } else {
>> return left == right;
>> }
>>}
>>>>>>If (iii) was valid we could never be certain to use "this" reference
> > inside
> >>methods, because it could be null. But the invariant of Object is
>>"assert(this !== null);".
>>>>>>---
>>Outgoing mail is certified Virus Free.
>>Checked by AVG anti-virus system (http://www.grisoft.com).
>>Version: 6.0.463 / Virus Database: 262 - Release Date: 17/3/2003
Maybe this has been suggested before, but this could all be avoided by not defining the == operator for the Object class. Thus, testing two objects for equivalence would typically be a compile-time error.
Classes that do define == would have to be mindful of null references, though.

I wholeheartedly agree. I need to be able to trust that I can use == and === without worrying about access violations for null pointers.
This is a big deal.
--Benji
In article <b5tsd9$1k9$1@digitaldaemon.com>, Matthew Wilson says...
>>Understood.
>>However, I have to strongly disagree with you - or is it Walter? - that the D comparison idiom is to eschew direct use of == and === and go for something like your equals(Object left, Object right).
>>This seems to be the worst of all possible worlds. D's bitten by its own cleverness here. Consider:
>>Java sucks because == works differently for fundamental and object types, necessitating the inconsistency of using equals() for object types.
>>C# attempts to address this with operator overloading, but it still leaves one clueless as to whether any reference comparisons are comparing equivalence or identity. This also sucks, unless one "trusts" the author of a class. (Got to love that trust ... it never let's you down, eh?)
>>C++ is entirely straightforward in that == on pointers always checks identity and on references/instances always checks equivalence. Notwithstanding this entirely reliable consistency, it is a source of confusion to novices simply because pointers and references are difficult concepts to absorb and disambiguate.
>>D sensibly addresses this confusing issue once and for all by separating equivalence and identity into separate operators, and making them syntatically consistent, hence my comment of satisfaction in the "Identity & equivalence" thread. However, this opportunity for clarity seems to have been thrown if what many of you are telling me is correct, which is that
>>class X
>{
>}
>>X y = . . .;
>>void f(X x)
>{
> if(x == y)
>>will cause an access violation if x or y are null. It makes a nonsense of the attempt to deal with the issue. If this cannot be fixed with == (and ===) syntax, then we're probably better off removing these operators from the language and forcing use of Object.equals() and Object.equalsRef(). For my part, though, that'd be gross. So:
>>It seems that we must find a way to sensibly incorporate === in ==. We could engineer the compiler, or mandate the class author, to incorporate identity checks in equivalence comparisons. However, there are circumstances in which the call to === would be superfluous, reducing efficiency.
>>afaics, we need to cater for the following scenarios
>>if(x == y)
>>(i) x and y are non-null
>(ii) x is null
>(iii) y is null
>(iv) x and y are null
>>the identity comparison _must_ be involved in == for (ii) and (iv), and
>probably for (iii). I suggest that classes have two comparison functions,
>both static, called eq() and eqi().
>>eq() compares two references for equality. It may assume that both references are guaranteed non-null. If eq() is not supported by a class, the compiler provides "a bit compare of the contents of the two structs is done to determine equality or inequality" as per the current fashion.
>>eqi() checks for references for equality, mindful of identity. It checks
>whether either or both of the references are null, and if they are not it
>returns the result of calling eq() on them. The default eqi() implementation
>would be
>>class X
>{
> static int eqi(X lhs, X rhs)
> {
> if(lhs === rhs) // Identity comparison
> {
> return true; // Same instance, therefore equal
> }
> else if(lhs === null &&
> rhs === null)
> {
> 0; // One, but not both (because of check above), is null, so not
>equal
> }
> else
> {
> return X.eq(lhs, rhs); // Both non-null, do equivalence comparison
> }
>>The author of a class can provide
>> - neither
> - eq() only - the usual case
> - eqi() only
> - eq() and eqi()
>>(I can't imagine a reason for either of the last two, but one never knows
>...)
>>It would be *entirely* up to the compiler as to whether it translates x == y
>as a call to X.eqi(x, y) or to X.eqi(x, y), presumably based on what it
>knows about the context of the two references. This means that any
>user-defined implementations of eq() and eqi() should be written with this
>in mind, presumably meaning that eqi() would do anything other than the
>default as shown above, apart from logging or other benign things.
>>Given static eq() and static eqi() we would no longer have non-static eq()
>as a special function, and could likely get rid of it. (I haven't thought
>about that part yet.)
>>Sound ok?
>>Comments appreciated. I certainly can't see how we can have access violations when using == in expressions!!
>>Matthew
>>>"Daniel Yokomiso" <daniel_yokomiso@yahoo.com.br> wrote in message news:b5s1hg$ca1$1@digitaldaemon.com...>> "Matthew Wilson" <dmd@synesis.com.au> escreveu na mensagem news:b5r9l2$2r2u$1@digitaldaemon.com...>> > In the documentation it says that the expression (a == b) is rewritten
>as
>> > a.equals(b).
>> >>> > Given that, what happens if a is null? NullPointerException, or is there
>a
>> > "safe-this" mechanism, ie. a.this is automatically checked against null?
>> If
>> > not automatic, is it possible to write something such as
>> >>> > class X
>> > {
>> > int value;
>> >>> > int eq(X rhs)
>> > {
>> > if(this === rhs)
>> > {
>> > return 1;
>> > }
>> > else if(rhs === null)
>> ^^^^^^ -> lhs
>>>> > {
>> > return false;
>> > }
>> > else if(rhs === null)
>> > {
>> > return false;
>> > }
>> > else
>> > {
>> > return this.value == rhs.value;
>> > }
>> > }
>> > }
>> >>> > (I expect I've got some syntax, wrong, but presumably you know what I
>> mean.)
>> >>> > This implementation then supports
>> >>> > X x1 = new X(1);
>> > X x2 = new X(3);
>> > X x3 = null;
>> >>> > if(x1 == x2) // (i)
>> > {}
>> > if(x1 == x3) // (ii)
>> > {}
>> > if(x3 == x2) // (iii)
>> > {}
>> >>> > If all this is not supported, what would be the result of (iii) - crash?
>> >>>>> Access violation. It's safer to use a generic equals function that will do the appropriate checks:
>>>>>> equals(Object left, Object right) {
>> if (left === right) {
>> return true;
>> } else if ((left === null) || (right === null)) {
>> return false;
>> } else {
>> return left == right;
>> }
>> }
>>>>>> If (iii) was valid we could never be certain to use "this" reference
>inside
>> methods, because it could be null. But the invariant of Object is
>> "assert(this !== null);".
>>>>>> ---
>> Outgoing mail is certified Virus Free.
>> Checked by AVG anti-virus system (http://www.grisoft.com).
>> Version: 6.0.463 / Virus Database: 262 - Release Date: 17/3/2003
>>>>>>

> This seems to be the worst of all possible worlds. D's bitten by its own cleverness here. Consider:
> Java sucks
> C# attempts
> C++ is entirely straightforward
Okay, I love C++ but something is entirely wrong if such an antiquated language manages to provide the most reliable solution to something as basic as this (at least if you're not a newbie).
I think anything we come up with is just going to be a kludge around a fundamental problem that is created by not automatically initializing objects when they are declared.
I wonder if it wouldn't be better to have the language implicitly create an
instance of the object when it is declared if it's not done explicitly:
blah blab;
blah blab=new blah; //these to lines are equivalent
//if blab is global, the instance would be created before main is called
You could then say that it is illegal to assign null to an object reference. I don't think it makes much sense to have a null object reference most of the time anyhow. And it is easy to add a "bit empty;" line to a class for the few times when something like that might be useful.
This would avoid the problem completely. This could in theory cause a performance hit, but I think that in practice that would happen seldom enough that it would be more than worth the robustness. How often do you declare something and don't instantiate it almost immediately? Even when you do, it's likely to be a one time only thing--that is to say, it's probably not going to be something that happens over and over again inside a loop or recursive function.

"Jon Allen" <jallen@minotstateu.edu> wrote in message news:b603qc$2ljo$1@digitaldaemon.com...> > This seems to be the worst of all possible worlds. D's bitten by its own cleverness here. Consider:
>> > Java sucks
> > C# attempts
> > C++ is entirely straightforward
>> Okay, I love C++ but something is entirely wrong if such an antiquated language manages to provide the most reliable solution to something as
basic
> as this (at least if you're not a newbie).
Why do you say it's basic? C#, D & Java all attempt to fudge the dichotomous nature of instances and their pointers for the sake of a (what some would say specious) syntactic nicety. For the record, I'm not one of them in principle, but no language other than C++ has yet managed this distinction in a sensible and complete fashion. D is coming the closest, but if we have to manually test for identity before we can test for equality then all that syntactic nicety is just a waste of time.
Whatever anyone's opinion, I fail to see how this is a simple issue.
> I think anything we come up with is just going to be a kludge around a fundamental problem that is created by not automatically initializing objects when they are declared.
Whether you like it or not, a big reason that C, C++ and D have been designed the way they have is for efficiency. C & C++ owe a lot of their sucecss to this. You're not going to sell D as anything other than a lame puppy if it cannot demonstrate similar, or better, levels of efficiency than its forebears/peers. The work Walter and I have been doing in the last week or so on performance comparisons has shown that D does indeed kick the arse of most of its peers in a number of areas; being a C++ diehard myself, this is a significant factor in my personnally starting to think more and more of using D seriously.
I know there is a significant subset of the C/C++ community who prefer the specious utility and "prettiness" of the iostreams and all those ridiculous chevron operators, but every single serious C++ developer I have worked with would run a mile before using them or any other inefficient thing. (Obviously I'm talking about development where performance is important. Other things one can be much more lax.) I am willing to bet large amounts of reputation that all these same experienced guys (and gals) won't look to D unless it can promise the same levels of performance.
> I wonder if it wouldn't be better to have the language implicitly create
an
> instance of the object when it is declared if it's not done explicitly:
> blah blab;
> blah blab=new blah; //these to lines are equivalent
> //if blab is global, the instance would be created before main is called
[Q: I presume that D currently initialises an uninitialised variable to null. Is this correct? If it doesn't, it should.]
I can't agree with this. Putting aside the fact that it could result in some horrible inefficiencies for non-trivial class instances, it is overly restrictive. Simplistically
class Something
{}
class QuiteSomething
: Something
{}
class AmazingSomething
: Something
{}
// Factory object
Something CreateSomethingSpecial(char[] parameterisation)
{
Something something = null; // The default case
if(parameterisation == "quite-special")
{
something = new QuiteSomething();
}
else if(parameterisation == "amazing")
{
something = new AmazingSomething();
}
else if
{
. . . // etc. etc.
return something;
}
What you're telling me is that I cannot write it like this. Either I have to have multiple return statements - which is not my style - or I have cannot return null so must use exceptions - which may not be appropriate/desirable. What have we gained with this restriction?
> You could then say that it is illegal to assign null to an object
reference.
> I don't think it makes much sense to have a null object reference most of the time anyhow.
I have to strongly disagree with this, sorry. :)
null is a _very_ useful state. Without it we'd either have to have signal objects, which is inefficient, or use exceptions for nearly everything.
> And it is easy to add a "bit empty;" line to a class for
> the few times when something like that might be useful.
>> This would avoid the problem completely. This could in theory cause a performance hit, but I think that in practice that would happen seldom enough that it would be more than worth the robustness. How often do you declare something and don't instantiate it almost immediately? Even when you do, it's likely to be a one time only thing--that is to say, it's probably not going to be something that happens over and over again inside
a
> loop or recursive function.
>>
All of the trouble so far is based on a desire to handle the mess of equivalence/identity in languages that use references as pointers (i.e. the references can be null). Unless there is an efficient built-in, but potentially user-tailorable, handling of this then we might as well get rid of the == and === operators entirely, and do everything with Object.Equals(o1, o2) and Object.EqualsReference(o1, o2) methods, with all the glamour that would entail.

Hi,
Comments embedded.
"Matthew Wilson" <dmd@synesis.com.au> escreveu na mensagem news:b5tsd9$1k9$1@digitaldaemon.com...> Understood.
>> However, I have to strongly disagree with you - or is it Walter? - that
the
> D comparison idiom is to eschew direct use of == and === and go for something like your equals(Object left, Object right).
Don't blame Walter for my lame opinions ;-) I prefer to play safe if I
don't know what the values can be. If a variable x can be null, I want to
deal with this directly. Also, if it can't be null (contracts are wonderful
aren't they?) I fell safe to use "==". But my lazyness compels me to use
only a generic equals, because it will be symmetrical in my "eq" methods and
deal with nasty things for me (like null or NaN).
> This seems to be the worst of all possible worlds. D's bitten by its own cleverness here. Consider:
>> Java sucks because == works differently for fundamental and object types, necessitating the inconsistency of using equals() for object types.
Java (the language) is consistent, because "==" handle equality (i.e. if
two things are the same) and "equals" handle equivalence (i.e. if right now
two things have same values). I'll ignore the distinction between primitives
and object types. OTOH Java (the library) sometimes assumes that "equals"
means identity on "value types" (an imaginary concept in Java, because the
VM knows only primitives and references, modulo magic string stuff). You can
see this erroneous behaviour in the collections framework, most classes
assume that "equals" and "hashCode" are invariant. Equivalence can be used
to check if two lists of same size have the equivalent items and the same
indices: it is a transient query. Identity can be used to check if two
variables have the same value (same primitive, struct or same reference). A
simple example is always nice:
alias bit boolean;
boolean equals(char[] left, char[] right) {
if (left === right) {
return true;
} else if ((null === left) || (null === right)) {
return false;
} else {
return left[] == right[];
}
}
class Customer {
private final long id;
private char[] name;
private int age;
this(long _id, char[] _name, int _age) {
this.id = _id;
this.name = _name;
this.age = _age;
}
public boolean eq(Customer other) {
return isEquivalentTo(other);
}
public boolean isSameAs(Customer other) {
return this === other;
}
public boolean isEquivalentTo(Customer other) {
if (other !== null) {
return (id == other.id) // primitive "==" is "==="
&& (age == other.age) // ditto
&& equals(name, other.name); // names can be null
} else {
return false;
}
}
public boolean isEqualTo(Customer other) {
if (other !== null) {
return (id == other.id); // our dreams ;-)
} else {
return false;
}
}
}
Customer charlie = new Customer(1, "Charlie", 8);
Customer mrBrown = new Customer(1, "Charlie", 27);
Customer peanutsDude = charlie;
Customer spuriousCharlie = new Customer(1, "Charlie", 8);
assert(charlie === peanutsDude);
assert(charlie.isSameAs(peanutsDude));assert(!charlie.isSameAs(mrBrown));assert(!charlie.isSameAs(spuriousCharlie));
assert(charlie == peanutsDude);
assert(charlie != mrBrown);
assert(charlie == spuriousCharlie);
assert(charlie.isEquivalentTo(peanutsDude));assert(!charlie.isEquivalentTo(mrBrown));assert(charlie.isEquivalentTo(spuriousCharlie));assert(charlie.isEqualTo(peanutsDude));assert(charlie.isEqualTo(mrBrown));assert(charlie.isEqualTo(spuriousCharlie));
In D identity is always variable value (primitive or struct or
reference). Equivalence is identity for primitives, "eq()" for objects and I
don't remember what for structs.
> C# attempts to address this with operator overloading, but it still leaves one clueless as to whether any reference comparisons are comparing equivalence or identity. This also sucks, unless one "trusts" the author
of
> a class. (Got to love that trust ... it never let's you down, eh?)
C# allows one to write beatiful code (i.e. cast-fest) to compare for
identity IIRC, something like "(o1 as Object) == (o2 as Object)".
> C++ is entirely straightforward in that == on pointers always checks identity and on references/instances always checks equivalence. Notwithstanding this entirely reliable consistency, it is a source of confusion to novices simply because pointers and references are difficult concepts to absorb and disambiguate.
I thought C++ depended on "operator==" definition's. Can't one define a
"operator==" for pointers that do the wrong thing?
> D sensibly addresses this confusing issue once and for all by separating equivalence and identity into separate operators, and making them syntatically consistent, hence my comment of satisfaction in the "Identity
&
> equivalence" thread. However, this opportunity for clarity seems to have been thrown if what many of you are telling me is correct, which is that
>> class X
> {
> }
>> X y = . . .;
>> void f(X x)
> {
> if(x == y)
>> will cause an access violation if x or y are null. It makes a nonsense of
> the attempt to deal with the issue. If this cannot be fixed with == (and
> ===) syntax, then we're probably better off removing these operators from
> the language and forcing use of Object.equals() and Object.equalsRef().
For
> my part, though, that'd be gross. So:
>> It seems that we must find a way to sensibly incorporate === in ==. We
could
> engineer the compiler, or mandate the class author, to incorporate
identity
> checks in equivalence comparisons. However, there are circumstances in
which
> the call to === would be superfluous, reducing efficiency.
>> afaics, we need to cater for the following scenarios
>> if(x == y)
>> (i) x and y are non-null
> (ii) x is null
> (iii) y is null
> (iv) x and y are null
>> the identity comparison _must_ be involved in == for (ii) and (iv), and
> probably for (iii). I suggest that classes have two comparison functions,
> both static, called eq() and eqi().
>> eq() compares two references for equality. It may assume that both
> references are guaranteed non-null. If eq() is not supported by a class,
the
> compiler provides "a bit compare of the contents of the two structs is
done
> to determine equality or inequality" as per the current fashion.
>> eqi() checks for references for equality, mindful of identity. It checks
> whether either or both of the references are null, and if they are not it
> returns the result of calling eq() on them. The default eqi()
implementation
> would be
>> class X
> {
> static int eqi(X lhs, X rhs)
> {
> if(lhs === rhs) // Identity comparison
> {
> return true; // Same instance, therefore equal
> }
> else if(lhs === null &&
> rhs === null)
> {
> 0; // One, but not both (because of check above), is null, so not
> equal
> }
> else
> {
> return X.eq(lhs, rhs); // Both non-null, do equivalence comparison
> }
>> The author of a class can provide
>> - neither
> - eq() only - the usual case
> - eqi() only
> - eq() and eqi()
>> (I can't imagine a reason for either of the last two, but one never knows
> ...)
>> It would be *entirely* up to the compiler as to whether it translates x ==
y
> as a call to X.eqi(x, y) or to X.eqi(x, y), presumably based on what it
> knows about the context of the two references. This means that any
> user-defined implementations of eq() and eqi() should be written with this
> in mind, presumably meaning that eqi() would do anything other than the
> default as shown above, apart from logging or other benign things.
>> Given static eq() and static eqi() we would no longer have non-static eq()
> as a special function, and could likely get rid of it. (I haven't thought
> about that part yet.)
>> Sound ok?
>> Comments appreciated. I certainly can't see how we can have access violations when using == in expressions!!
Perhaps we should try to see this with D colored glasses. If I say this:
boolean foo(Object bar, Object baz)
in {
assert(bar !== null);
assert(baz !== null);
} body {
return bar == baz;
}
I "know" that it is correct. The contract defines that only when bar and
baz are non-null something sensible will happen. If they're null foo can do
anything it wants to (usually just throw an assertion error, but if
contracts are disabled evil things can happen). OTOH if I say this:
boolean foo(Object bar, Object baz) {
return (bar === null) ? (bar === baz) : (bar == baz);
}
I "must" deal with the possibility of getting strange values for bar or
baz, so if I don't think carefully I'm assuming something without any
"proof" (e.g. contracts) about it.
We know that if "a === b", a can be substituted by b anywhere. Also, we
know that "a === b" implies "a == b". This is pretty much what we can say
about equivalence (I don't even think that equivalence must be symmetric but
I may be alone here). This can be expressed in terms of contracts in
Object.eq(Object):
public bit eq(Object other)
in {
assert(other !== null);
} out(result) {
if (this === other) {
assert(result);
}
if (null === other) {
// an evil subclass may relax the precondition, but nothing should be eq to
null
assert(!result);
}
}
Working on a language with contracts requires some changes of habit. If
we did something using defensive programming (e.g. accepting nulls and
returning false) now we should use offensive programming (e.g. reject it as
a programming error using contracts). Smalltalk has a Nil object
representing null values that responds to every method with
doesNotUnderstand (similar to NullPointerException or access violation), but
you can make it answer to any method with a Nil value, propagating the
nullness. It may be nice to have an answer that leads to less checks and
simpler code (google for Null Object Pattern), but it can propagate errors
silently, a la NaN in floating point operations. IMO calling "==" is just
sugar for calling "eq", so default restrictions apply too.
I want to make clear that this mistake is common. Perhaps 1/3 of my bugs
in D are due to using "==" on null variables, instead of the correct
checks/contracts, but the solution isn't make this bug magically disappear
and silently introduce others. We must change our thinking habits when using
D and make heavy use of contracts. While I agree that checking for "null"
may be tedious, it's the D way of doing things (Eiffel works like this too:
their libraries are full of "require other_not_void: other /= Void"). The
Nice language ( http://nice.sourceforge.net ) has a better solution, letting
null values only in types marked with "?" and requiring explicit checks.
Perhaps I should write "Null pointers considered harmful" someday ;-)
>> Matthew
>[snip]
Best regards,
Daniel Yokomiso.
"I'm less of a neurotic perfectionist than I was. But I don't think that
anyone who has done good work in their life isn't a perfectionist. You have
to be."
- John Cleese
---
Outgoing mail is certified Virus Free.
Checked by AVG anti-virus system (http://www.grisoft.com).
Version: 6.0.463 / Virus Database: 262 - Release Date: 18/3/2003