The short answer: Consistency
To answer your question properly, though, I suggest we take a step backwards and look to the issue of what equality means in a programming language. There are at least THREE different possibilities, which are used in various languages:
- Reference equality: means that a = b is true if a and b refer to the same object. It would not be true if a and b referred to different objects, even if all the attributes of a and b were the same.
- Shallow equality: means that a = b is true if all the attributes of the objects to which a and b refer are identical. Shallow equality can easily be implemented by a bitwise comparison of the memory space that represents the two objects. Please note that reference equality implies shallow equality
- Deep equality: means that a = b is true if each attribute in a and b is either identical or deeply equal. Please note that deep equality is implied by both reference equality and shallow equality. In this sense, deep equality is the weakest form of equality and reference equality is the strongest.
These three types of equality are often used because they are convenient to implement: all three equality checks can easily be generated by a compiler (in the case of deep equality, the compiler might need to use tag bits to prevent infinite loops if a structure to be compared has circular references). But there is another problem: none of these might be appropriate.
In non-trivial systems, equality of objects is often defined as something between deep and reference equality. To check whether we want to regard two objects as equal in a certain context, we might require some attributes to be compared by where it stands in memory and others by deep equality, while some attributes may be allowed to be something different altogether. What we would really like is a “forth type of equality”, a really nice one, often called in the literature semantic equality. Things are equal if they are equal, in our domain. =)
So we can come back to your question:
Is there some major benefit of defaulting to this that I am simply missing, or does
it seem reasonable that the default behavior should be logical equality,
and defaulting back to reference equality if a logical equality doesn't exist
for the class?
What do we mean when we write ‘a == b’ in any language? Ideally, it should always be the same: Semantic equality. But that´s not possible.
One of the main considerations is that, at least for simple types like numbers, we expect that two variables are equal after assignment of the same value. See below:
var a = 1;
var b = a;
if (a == b){
...
}
a = 3;
b = 3;
if (a == b) {
...
}
In this case, we expect that ‘a equals b’ in both statements. Anything else would be insane. Most (if not all) of the languages follow this convention. Therefore, with simple types (aka values) we know how to achieve semantic equality. With objects, that can be something completely different. See below:
var a = new Something(1);
var b = a;
if (a == b){
...
}
b = new Something(1);
a.DoSomething();
b.DoSomething();
if (a == b) {
...
}
We expect that the first ‘if’ will always be true. But what do you expect on the second ‘if’? It really depends. Can ‘DoSomething’ change the (semantic) equality of a and b?
The problem with semantic equality is that it cannot be automatically generated by the compiler for objects, nor it´s obvious from the assignments. A mechanism must be provided for the user to define semantic equality. In object-oriented languages, that mechanism is an inherited method: equals. Reading a piece of OO code, we don´t expect a method to have the same exact implementation in all classes. We are used to inheritance and overloading.
With operators, though, we expect the same behavior. When you see ‘a == b’ you should expect the same type of equality (from the 4 above) in all situations. So, aiming for consistency the languages designers used reference equality for all types. It should not depend on whether a programmer has overridden a method or not.
PS: The language Dee is slightly different from Java and C#: the equals operator means shallow equality for simple types and semantic equality for user-defined classes (with the responsibility for implementing the = operation lying with the user — no default is provided). As, for simple types, shallow equality is always semantic equality, the language is consistent. The price it pays, though, is that the equals operator is by default undefined for user-defined types. You have to implement it. And, sometimes, that´s just boring.
Equals()
, it doesn't automatically change the behavior of==
?record
keyword instead ofclass
) actually do default to deep equality. This works because records are immutable carriers of data, not stateful objects with behavior.