Comparison of objects
*** *** Please if you read French, translate the corresponding page using the French version as a model. *** If you decide to translate this page, start to add here a *** note in order to avoid multiple *** translators. *** Please, check also our Author guidelines first ! *** Thanks in advance. *** Colnet 11:59, 6 Sep 2005 (CEST) *** *** Well, I've made a small start, but others are welcome (as far as I'm concerned) to contribute. *** I'm having fun translating it, but this could take some days! *** Peter Gummer 6 Sep 2005 *** *** I've done the next section. I will flag up here before starting on another one. *** Oliver Elphick, 7 Sep 2005 *** *** Thank you all for the great work. Colnet 09:41, 7 Sep 2005 (CEST) ***
Comparing two objects
How to compare two objects is a problem that has to be mastered fairly quickly, for it's a very common problem posed in many applications, if not almost all applications.
In Eiffel, as in most object-oriented languages, there are several ways to compare two objects, be it to compare their respective addresses or be it to compare the information contained in each of the two objects. In other words, using Eiffel terminology, one must choose whether to use the predefined = operator or the redefinable is_equal routine.
Because the Eiffel language aims, as far as possible, to prevent careless mistakes, it isn't possible to compare any type of expression with any other type of expression. The validity of a comparison will be detailed below.
Finally, just to be complete, this part will also discuss the possibility of carrying out a third sort of comparison, deep equality, a comparison uniquely reserved for a very rare family of applications.
Comparison using the operators = or /=
Let there be two variables point_a and point_b both of type POINT. Let us suppose that these two variables have been initialised with something other than Void. The following code fragment shows a possible usage of the predefined = comparison operator:
if point_a = point_b then io.put_string("We have a single, unique POINT !") else io.put_string("We have two POINTs at different memory locations.") end
Comparison using the = operator is the most basic building block for comparison. In the case of a reference type, it corresponds simply to the direct comparison of the two addresses. What the two addresses point to is not considered. If our two variables point_a and point_b actually designate a single, unique POINT in memory, then the test above evaluates to true. Once there are two distinct POINTs in memory, then the test is false even if the two POINTs seem exactly identical, in the debugger for example, or when printed.
Note that we also have the /= operator, which corresponds to the negation of the = operator. The /= operator is also predefined and is simply a shortcut to avoid having to use not for negation. So to test whether a reference variable refers to an object, the usage is:
if point /= Void then io.put_string("The variable point refers to a POINT!") else io.put_string("The variable point does not refer to any object.") end
When comparing variables of an expanded type, the effect of the = and /= operators is simply to compare the values in question. For two variables of type INTEGER, that boils down to comparing the two numbers:
if integer_a = integer_b then io.put_string("integer_a is the same value as integer_b!") else io.put_string("The two values are different.") end
The essential details about the = and /= predefined operators have now been clearly explained above, at least, so we hope. If you are reading this to find out about comparison methods, go straight to the section on is_equal. Otherwise, continue to the next section for additional information about these two predefined operators.
The = and /= operators with expanded types
The = and /= operators, apart from being predefined, are non-redefinable operators. The effect of = and /= is completely fixed, within the compiler.
When comparing two expressions of a reference type with the = or /= operators, as has already been mentioned above, only the two addresses are compared. Let's see what happens when the two compared expressions are of an expanded type.
Comparing two expanded expressions using = simply comes down to comparing, at the lowest level, bit by bit, the two areas of memory corresponding to the two objects. Of course, the expanded type used on the left of the = operator must be compatible with the expanded type used on the right. Indeed, apart from some rare exceptions that we'll detail later, a comparison is accepted only when the two expanded types are exactly the same. It's obviously possible to compare two expressions of type CHARACTER with each other; and, for example, to compare two expressions of type INTEGER with each other. In order to avoid any careless mistakes, however, directly comparing a CHARACTER with an INTEGER is forbidden. Note that in the latter case, it's up to the program's designer to make a call to the appropriate conversion routine. There are multiple possibilities, be it to convert the INTEGER expression into a CHARACTER, or be it, inversely, to convert the CHARACTER expression into an INTEGER. In both cases the possible conversion routines are themselves numerous. Several routines exist, for example, to convert from CHARACTER to INTEGER. Here are two such examples, among many others:
if my_character.to_integer = my_integer then io.put_string("Conversion with possible sign extension.") end
and again:
if my_character.decimal_value = my_integer then io.put_string("Conversion using the value of the corresponding number.") end
Thus, it seems that it's not absolutely possible to automate the choice of conversion that ought to be applied before the comparison. That depends completely on the context of the comparison, on the problem being dealt with, and this choice must be made manually by the program's designer. The strict rule, consisting of allowing the comparison of two expanded objects when they have exactly the same type, is indeed the simplest and safest. Thus, if the compiler rejects your comparison expression, read the error message carefully and choose the most appropriate conversion with care. Having said this, probably for historical reasons, two exceptions to this very strict rule subsist.
The two exceptions concern two special cases where no doubt is permitted given that the expanded objects concerned are very elementary. The first exception to the strict rule of the identity of two expanded types concerns the case of two objects taken from the set {INTEGER_8, INTEGER_16, INTEGER_32, INTEGER, INTEGER_64}. Comparison using = is true if and only if the two values correspond to exactly the same integer value. In fact, without any loss of information, it is always possible to represent in 16 bits any number that is represented in 8 bits; and so on and so forth. This exception to the hard and fast rule therefore lets us write simply and without risk:
if my_integer_8 = my_integer_16 then ...
knowing that it's as if we had written:
if my_integer_8.to_integer_16 = my_integer_16 then ...
The second and last exception is similar to the preceding one and concerns the following set of expanded types: {REAL_32, REAL_64, REAL, REAL_80, REAL_128, REAL_EXTENDED}. Like the integers, for the set of floating point numbers in question, it is always possible to convert a given floating point number to a bigger number of bits without any loss of information. Thus, a comparison expression using = or /= can mix two types taken from the REAL_* family.
Finally, note that it is forbidden to compare directly with each other any object from the REAL_* family with any object from the INTEGER_* family. If you are led to having to make such a comparison, which isn't illogical, it's up to you to decide which conversion routine is appropriate to your needs. The Eiffel language has no rule that could decide this for you.