Difference between revisions of "Comparison of objects"

From Liberty Eiffel Wiki
Jump to navigation Jump to search
(Notice of starting to translate final sections)
(Finish translation; remove some inappropriate colloquialisms; make some phrases more idiomatic.)
Line 1: Line 1:
 
[[Category:Book]]
 
[[Category:Book]]
   
  +
*** Translation completed 7 Nov 2005
***
 
  +
*** Will a French speaker please review to ensure that the
*** Please if you read French, translate the corresponding page using the French version as a model.
 
  +
*** sense has been properly conveyed.
*** 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.
 
*** [[User:Colnet|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. [[User:Colnet|Colnet]] 09:41, 7 Sep 2005 (CEST)
 
***
 
*** Still not finished, but getting close!
 
*** Peter Gummer 12 Sep 2005
 
***
 
*** Embarking on the last bits
 
*** Oliver Elphick 7 Nov 2005
 
   
 
==Comparing two objects==
 
==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'''.
+
How to compare two objects is a problem that has to be mastered fairly quickly, for it is a very common problem posed in many, 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 [[#EqNeq|the predefined = operator]] or the redefinable [[#is_equal|<TT>is_equal</TT>]] routine.
+
In Eiffel, as in most object-oriented languages, there are several ways to compare two objects, whether by comparing their respective addresses or by comparing the information contained in each of the two objects. In other words, using Eiffel terminology, one must choose whether to use [[#EqNeq|the predefined = operator]] or the redefinable [[#is_equal|<TT>is_equal</TT>]] 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 [[#ValidComparison|validity of a comparison]] will be detailed below.
+
Because the Eiffel language aims, as far as possible, to prevent careless mistakes, it is not possible to compare just any type of expression with any other type of expression. The [[#ValidComparison|validity of a comparison]] will be explained in detail below.
   
Finally, just to be complete, this part will also discuss the possibility of carrying out a third sort of comparison, [[#is_deep_equal|deep equality]], a comparison uniquely reserved for a very rare family of applications.
+
Finally, just for completeness, this part will also discuss the possibility of carrying out a third sort of comparison, [[#is_deep_equal|deep equality]], a comparison reserved only for a very rare family of applications.
 
<div id="EqNeq">
 
<div id="EqNeq">
   
Line 47: Line 28:
 
io.put_string("We have two POINTs at different memory locations.")
 
io.put_string("We have two POINTs at different memory locations.")
 
end
 
end
Comparison using the <TT>=</TT> operator is the most basic building block for
+
The <TT>=</TT> operator is the fundamental building block for
 
comparison.
 
comparison.
 
In the case of a reference type, it corresponds simply to the direct comparison of
 
In the case of a reference type, it corresponds simply to the direct comparison of
the two addresses.
+
the two addresses;
What the two addresses point to is not considered.
+
what the two addresses point to is not considered.
 
If our two variables <TT>point_a</TT> and <TT>point_b</TT> actually designate
 
If our two variables <TT>point_a</TT> and <TT>point_b</TT> actually designate
 
a single, unique <TT>POINT</TT> in memory, then the test above evaluates to true.
 
a single, unique <TT>POINT</TT> in memory, then the test above evaluates to true.
Line 88: Line 69:
 
==The <TT>=</TT> and <TT>/=</TT> operators with expanded types==
 
==The <TT>=</TT> and <TT>/=</TT> operators with expanded types==
 
</div>
 
</div>
The <TT>=</TT> and <TT>/=</TT> operators, apart from being predefined, are '''non-redefinable''' operators. The effect of <TT>=</TT> and <TT>/=</TT> is completely fixed, within the compiler.
+
The <TT>=</TT> and <TT>/=</TT> operators, apart from being predefined, are '''non-redefinable''' operators. The effect of <TT>=</TT> and <TT>/=</TT> is completely fixed in the compiler's internals.
   
When comparing two expressions of a reference type with the <TT>=</TT> or <TT>/=</TT> 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.
+
When comparing two expressions of a reference type with the <TT>=</TT> or <TT>/=</TT> operators, as has already been mentioned above, only the two addresses are compared. Now let us see what happens when the two compared expressions are of an expanded type.
   
Comparing two expanded expressions using <TT>=</TT> 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 <TT>=</TT> operator must be compatible with the expanded type used on the right. Indeed, apart from some [[#ExceptionalRules|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
+
Comparing two expanded expressions using <TT>=</TT> 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 <TT>=</TT> operator must be compatible with the expanded type used on the right. Indeed, apart from some [[#ExceptionalRules|rare exceptions]] which we will go into later, a comparison is accepted only when the two expanded types are exactly the same. It is obviously possible to compare two expressions of type
[[library_class:CHARACTER|<tt>CHARACTER</tt>]] with each other; and, for example, to compare two expressions of type [[library_class:INTEGER|<tt>INTEGER</tt>]] with each other. In order to avoid any careless mistakes, however, directly comparing a [[library_class:CHARACTER|<tt>CHARACTER</tt>]] with an [[library_class:INTEGER|<tt>INTEGER</tt>]] 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 <tt>INTEGER</tt> expression into a <tt>CHARACTER</tt>, or be it, inversely,
+
[[library_class:CHARACTER|<tt>CHARACTER</tt>]] with each other; and, for example, to compare two expressions of type [[library_class:INTEGER|<tt>INTEGER</tt>]] with each other. In order to avoid any careless mistakes, however, directly comparing a [[library_class:CHARACTER|<tt>CHARACTER</tt>]] with an [[library_class:INTEGER|<tt>INTEGER</tt>]] is forbidden. In the latter case, it is up to the program's designer to make a call to the appropriate conversion routine. There are multiple possibilities, perhaps to convert the <tt>INTEGER</tt> expression into a <tt>CHARACTER</tt>, or, contrariwise,
to convert the <tt>CHARACTER</tt> expression into an <tt>INTEGER</tt>. In both cases the possible conversion routines are themselves numerous. Several routines exist, for example, to convert from <tt>CHARACTER</tt> to <tt>INTEGER</tt>. Here are two such examples, among many others:
+
to convert the <tt>CHARACTER</tt> expression into an <tt>INTEGER</tt>. In both cases there are many possible conversion routines. Several routines exist, for example, to convert from <tt>CHARACTER</tt> to <tt>INTEGER</tt>. Here are two such examples, among many others:
 
if my_character.to_integer = my_integer then
 
if my_character.to_integer = my_integer then
 
io.put_string("Conversion with possible sign extension.")
 
io.put_string("Conversion with possible sign extension.")
Line 102: Line 83:
 
io.put_string("Conversion using the value of the corresponding number.")
 
io.put_string("Conversion using the value of the corresponding number.")
 
end
 
end
Thus, it seems that it's absolutely not possible to automate the choice of conversion that ought to be applied before the comparison. It 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 only 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, two exceptions to this very strict rule remain, probably for historical reasons.
+
Thus, it seems completely impossible to automate the choice of conversion that should be applied before the comparison. It depends entirely 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, to allow the comparison of two expanded objects only 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. That being said, two exceptions to this very strict rule remain, probably for historical reasons.
   
 
<div id="ExceptionalRules">
 
<div id="ExceptionalRules">
The two exceptions concern two special cases that '''leave no doubt''' thanks to the fact 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 <tt>{</tt>[[library_class:INTEGER_8|<tt>INTEGER_8</tt>]], [[library_class:INTEGER_16|<tt>INTEGER_16</tt>]], [[library_class:INTEGER_32|<tt>INTEGER_32</tt>]], [[library_class:INTEGER|<tt>INTEGER</tt>]], [[library_class:INTEGER_64|<tt>INTEGER_64</tt>]]<tt>}</tt>. Comparison using <tt>=</tt> 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:
+
The two exceptions concern two special cases that '''leave no doubt''' thanks to the fact that the expanded objects concerned are very elementary. The first exception to the strict rule, requiring two expanded types to be identical, concerns the case of two objects taken from the set <tt>{</tt>[[library_class:INTEGER_8|<tt>INTEGER_8</tt>]], [[library_class:INTEGER_16|<tt>INTEGER_16</tt>]], [[library_class:INTEGER_32|<tt>INTEGER_32</tt>]], [[library_class:INTEGER|<tt>INTEGER</tt>]], [[library_class:INTEGER_64|<tt>INTEGER_64</tt>]]<tt>}</tt>. Comparison using <tt>=</tt> 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 ...
 
if my_integer_8 = my_integer_16 then ...
knowing that it's as if we had written:
+
knowing that it is as if we had written:
 
if my_integer_8.to_integer_16 = my_integer_16 then ...
 
if my_integer_8.to_integer_16 = my_integer_16 then ...
 
</div>
 
</div>
Line 113: Line 94:
 
The second and last exception is similar to the preceding one and concerns the following set of expanded types: <tt>{</tt>[[library_class:REAL_32|<tt>REAL_32</tt>]], [[library_class:REAL_64|<tt>REAL_64</tt>]], [[library_class:REAL|<tt>REAL</tt>]], [[library_class:REAL_80|<tt>REAL_80</tt>]], [[library_class:REAL_128|<tt>REAL_128</tt>]], [[library_class:REAL_EXTENDED|<tt>REAL_EXTENDED</tt>]]<tt>}</tt>. 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 <tt>=</tt> or <tt>/=</tt> can mix two types taken from the <tt>REAL_*</tt> family.
 
The second and last exception is similar to the preceding one and concerns the following set of expanded types: <tt>{</tt>[[library_class:REAL_32|<tt>REAL_32</tt>]], [[library_class:REAL_64|<tt>REAL_64</tt>]], [[library_class:REAL|<tt>REAL</tt>]], [[library_class:REAL_80|<tt>REAL_80</tt>]], [[library_class:REAL_128|<tt>REAL_128</tt>]], [[library_class:REAL_EXTENDED|<tt>REAL_EXTENDED</tt>]]<tt>}</tt>. 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 <tt>=</tt> or <tt>/=</tt> can mix two types taken from the <tt>REAL_*</tt> family.
   
Note, finally, that it is forbidden to compare directly any object from the <tt>REAL_*</tt> family with any object from the <tt>INTEGER_*</tt> family. If you find yourself 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.
+
Note, finally, that it is forbidden to compare directly any object from the <tt>REAL_*</tt> family with any object from the <tt>INTEGER_*</tt> family. If you find yourself having to make such a comparison, which is not illogical, it is 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.
   
 
<div id="is_equal">
 
<div id="is_equal">
Line 119: Line 100:
 
==The redefinable <tt>is_equal</tt> routine==
 
==The redefinable <tt>is_equal</tt> routine==
 
</div>
 
</div>
The redefinable <tt>is_equal</tt> routine provides a less elementary comparison than using [[#EqNeq|the predefined = operator]]. Consider once again our example of comparing two variables of type <tt>POINT</tt>, but this time let's use <tt>is_equal</tt>. Like the above case, let's suppose for the moment that the two variables <tt>point_a</tt> and <tt>point_b</tt> are initialised with something other than [[Void|<tt>Void</tt>]]:
+
The redefinable <tt>is_equal</tt> routine provides a more sophisticated comparison than using [[#EqNeq|the predefined = operator]]. Consider once again our example of comparing two variables of type <tt>POINT</tt>, but this time let us use <tt>is_equal</tt>. As in the case above, let us suppose for the moment that the two variables <tt>point_a</tt> and <tt>point_b</tt> are initialised with something other than [[Void|<tt>Void</tt>]]:
 
if point_a.is_equal(point_b) then
 
if point_a.is_equal(point_b) then
 
io.put_string("Either they are both the same POINT or else they are 2 identical POINTs.")
 
io.put_string("Either they are both the same POINT or else they are 2 identical POINTs.")
Line 125: Line 106:
 
io.put_string("They are two different POINTs.")
 
io.put_string("They are two different POINTs.")
 
end
 
end
In a given class, if the <tt>is_equal</tt> routine hasn't been redefined by the user, the behaviour of <tt>is_equal</tt> consists of comparing all attributes of the two objects with each other. That means, for the <tt>POINT</tt> example, successively comparing the two <tt>x</tt> attributes and then the two <tt>y</tt> attributes. In other words, it's as if we'd written:
+
In a given class, if the <tt>is_equal</tt> routine has not been redefined by the user, the behaviour of <tt>is_equal</tt> is to compare all attributes of the two objects with each other. That means, for the <tt>POINT</tt> example, successively comparing the two <tt>x</tt> attributes and then the two <tt>y</tt> attributes. In other words, it is as if we had written:
 
if (point_a.x = point_b.x) and (point_a.y = point_b.y) then
 
if (point_a.x = point_b.x) and (point_a.y = point_b.y) then
 
io.put_string("Either they are both the same POINT or else they are 2 identical POINTs.")
 
io.put_string("Either they are both the same POINT or else they are 2 identical POINTs.")
Line 131: Line 112:
 
io.put_string("They are two different POINTs.")
 
io.put_string("They are two different POINTs.")
 
end
 
end
Note how the comparison between attributes does not use <tt>is_equal</tt>, but the elementary fixed <tt>=</tt> operator. The following diagram presents a picture of the possible memory contents during comparison of <tt>point_a</tt> and <tt>point_b</tt>. Note that in the following case, these two <tt>POINT</tt>s situated in two memory locations are identical and, consequently, comparison using <tt>is_equal</tt> will return <tt>True</tt>:
+
See how the comparison between attributes does not use <tt>is_equal</tt>, but the basic fixed <tt>=</tt> operator. The following diagram presents a picture of the possible memory contents during comparison of <tt>point_a</tt> and <tt>point_b</tt>. Note that in the following case, these two <tt>POINT</tt>s situated in two memory locations are identical and, consequently, comparison using <tt>is_equal</tt> will return <tt>True</tt>:
   
 
[[Image:IsEqualPoints.png|is_equal with some POINTs]]
 
[[Image:IsEqualPoints.png|is_equal with some POINTs]]
   
In the example above, the two objects that are being compared are both instances of the <tt>POINT</tt> class: very simple objects. As the diagram shows, a <tt>POINT</tt> is composed of two expanded attributes, or, more exactly, the attributes <tt>x</tt> and <tt>y</tt> of type [[library_class:REAL|<tt>REAL</tt>]], a primitive expanded type (i.e. without an intermediate pointer). In this case the default definition of the <tt>is_equal</tt> function fits perfectly.
+
In the example above, the two objects that are being compared are both instances of the <tt>POINT</tt> class: very simple objects. As the diagram shows, a <tt>POINT</tt> is composed of two expanded attributes, or, more precisely, the attributes <tt>x</tt> and <tt>y</tt> of type [[library_class:REAL|<tt>REAL</tt>]], a primitive expanded type (i.e. without an intermediate pointer). In this case the default definition of the <tt>is_equal</tt> function fits perfectly.
   
Before describing <tt>is_equal</tt> any further, let's be clear that <tt>is_equal</tt> can be used when the type of the two compared
+
Before describing <tt>is_equal</tt> any further, let us make it clear that <tt>is_equal</tt> can be used when the type of the two compared
objects is expanded. For example, with [[library_class:INTEGER|<tt>INTEGER</tt>]]s, the result of a comparison using the predefined <tt>=</tt> operator has exactly the same effect as using the <tt>is_equal</tt> routine. Note however that using <tt>is_equal</tt> is quite unusual when the two operands are expanded, especially if it's a basic expanded type like [[library_class:INTEGER|<tt>INTEGER</tt>]], [[library_class:CHARACTER|<tt>CHARACTER</tt>]], [[library_class:REAL|<tt>REAL</tt>]] or [[library_class:BOOLEAN|<tt>BOOLEAN</tt>]]. In general, by convention and especially for the sake of simplicity, <tt>=</tt> and <tt>/=</tt> are preferred for primitive expanded types.
+
objects is expanded. For example, with [[library_class:INTEGER|<tt>INTEGER</tt>]]s, the result of a comparison using the predefined <tt>=</tt> operator has exactly the same effect as using the <tt>is_equal</tt> routine. Note however that using <tt>is_equal</tt> is quite unusual when the two operands are expanded, especially if it is a basic expanded type like [[library_class:INTEGER|<tt>INTEGER</tt>]], [[library_class:CHARACTER|<tt>CHARACTER</tt>]], [[library_class:REAL|<tt>REAL</tt>]] or [[library_class:BOOLEAN|<tt>BOOLEAN</tt>]]. In general, by convention and especially for the sake of simplicity, <tt>=</tt> and <tt>/=</tt> are preferred for primitive expanded types.
   
As soon as the objects are more complex, with for example some attributes that themselves point towards other objects, it's necessary to pay attention to the predefined behaviour of the <tt>is_equal</tt> function. Consider for example the case of comparing two <tt>TRIANGLE</tt>s, as in the following figure:
+
As soon as the objects are more complex, with for example some attributes that themselves point towards other objects, it is necessary to pay attention to the predefined behaviour of the <tt>is_equal</tt> function. Consider for example the case of comparing two <tt>TRIANGLE</tt>s, as in the following diagram:
   
 
[[Image:IsEqualTriangles.png|is_equal with some TRIANGLEs]]
 
[[Image:IsEqualTriangles.png|is_equal with some TRIANGLEs]]
   
Both objects of class <tt>TRIANGLE</tt> in the figure above are identical but if one procedes to compare them using the predefined <tt>is_equal</tt> routine the result will be <tt>False</tt>! In fact, let's emphasise that <tt>triangle_a</tt> and <tt>triangle_b</tt> designate, even if they are similar, two distinct objects in memory. Indeed, if the <tt>is_equal</tt> function has not been redefined in the <tt>TRIANGLE</tt> class, the comparison of <tt>triangle_a</tt> and <tt>triangle_b</tt> using <tt>is_equal</tt>, in other words the [[Glossary#Expression|expression]]:
+
Both objects of class <tt>TRIANGLE</tt> in the diagram above are identical but if one proceeds to compare them using the predefined <tt>is_equal</tt> routine the result will be <tt>False</tt>! In fact, let us emphasise that <tt>triangle_a</tt> and <tt>triangle_b</tt> designate, even if they are similar, two distinct objects in memory. Indeed, if the <tt>is_equal</tt> function has not been redefined in the <tt>TRIANGLE</tt> class, the comparison of <tt>triangle_a</tt> and <tt>triangle_b</tt> using <tt>is_equal</tt>, in other words the [[Glossary#Expression|expression]]:
   
 
triangle_a.is_equal(triangle_b)
 
triangle_a.is_equal(triangle_b)
Line 152: Line 133:
 
(triangle_a.p1 = triangle_b.p1) and (triangle_a.p2 = triangle_b.p2) and (triangle_a.p3 = triangle_b.p3)
 
(triangle_a.p1 = triangle_b.p1) and (triangle_a.p2 = triangle_b.p2) and (triangle_a.p3 = triangle_b.p3)
   
The expression above corresponds to the predefined behaviour of <tt>is_equal</tt>. In order to obtain a more useful comparison function, it's up to the designer of the <tt>TRIANGLE</tt> class to think of how to alter the predefined definition by redefining <tt>TRIANGLE</tt>'s <tt>is_equal</tt> function like this:
+
The expression above corresponds to the predefined behaviour of <tt>is_equal</tt>. In order to obtain a more useful comparison function, it is up to the designer of the <tt>TRIANGLE</tt> class to think of how to alter the predefined definition by redefining <tt>TRIANGLE</tt>'s <tt>is_equal</tt> function like this:
   
 
is_equal (other: TRIANGLE): BOOLEAN is
 
is_equal (other: TRIANGLE): BOOLEAN is
Line 159: Line 140:
 
end
 
end
   
Note that it's very hard to imagine how a ''good function'' for comparison could be defined automatically. In particular, it's not always enough to call <tt>is_equal</tt> again on the attributes, as in the <tt>TRIANGLE</tt> case, rather than using <tt>=</tt>. (To convince youself of this, see the [[library_class:STRING|<tt>STRING</tt>]] and [[library_class:ARRAY|<tt>ARRAY</tt>]] classes as examples.) Thus, without knowing how to automate this task with a language rule or by adding a strong dose of intelligence to the compiler, the job belongs solely to the designer of the class. The conscientious designer of a class must take care of the good behaviour of the <tt>is_equal</tt> function for objects of that class. The default behaviour described above has been chosen for its simplicity, because it suits often enough as a ''good'' comparison function and also because it is implemented easily in low level machine instructions that are very fast (comparison instructions for two memory blocks).
+
It is very hard to imagine how a ''good function'' for comparison could be defined automatically. In particular, it is not always enough to call <tt>is_equal</tt> again on the attributes, as in the <tt>TRIANGLE</tt> case, rather than using <tt>=</tt>. (To convince youself of this, see the [[library_class:STRING|<tt>STRING</tt>]] and [[library_class:ARRAY|<tt>ARRAY</tt>]] classes as examples.) Thus, since we do not know how to automate this task with a language rule or by adding a strong dose of intelligence to the compiler, the job belongs solely to the designer of the class. The conscientious class designer must ensure that the <tt>is_equal</tt> function behaves properly for objects of that class. The default behaviour described above has been chosen for its simplicity, because it is is for the most part suitable as a ''good'' comparison function and also because it is implemented easily in low level machine instructions that are very fast (comparison instructions for two memory blocks).
   
 
<div id="ValidComparison">
 
<div id="ValidComparison">
   
  +
==Comparison validity==
==Validité des comparaisons==
 
 
</div>
 
</div>
  +
Do not look for the definition of the [[#EqNeq|<tt>=</tt> and <tt>/=</tt>]]
Ne cherchez pas la définition des opérateurs de comparaison
 
  +
comparison operators in the Eiffel classes' source
[[#EqNeq|<tt>=</tt> et <tt>/=</tt>]] dans le code source Eiffel des classes car ces deux
 
  +
code, because these two operators are predefined and their definition is deliberately fixed in the compiler's internals.
opérateurs sont prédéfinis et leur définition est volontairement figée à l'intérieur même
 
  +
Since one of the goals of the Eiffel language is to limit the possibility of careless errors,
du compilateur.
 
  +
it would not be reasonable to allow just any comparison (that is, to allow all mixtures, like
Un des buts du langage Eiffel consistant à limiter les erreurs d'inattention,
 
  +
comparing a <tt>LORRY</tt> with a
il ne serait pas raisonnable d'autoriser n'importe quelle comparaison (i.e. d'autoriser
 
  +
<tt>FLOWER</tt>, to take an extreme example).
tous les mélanges comme par exemple la comparaison d'un <tt>CAMION</tt> avec une <tt>FLEUR</tt>
 
  +
The [[Glossary#StaticType|static types]] of two [[Glossary#Expression|expressions]] to be
pour prendre un cas extrême).
 
  +
compared must conform to certain constraints.
Les [[Glossary#StaticType|types statiques]] des deux [[Glossary#Expression|expressions]] que
 
  +
Of course -- and this is the usual case -- when the two expressions are of the same
l'on compare doivent respecter certaines contraintes.
 
  +
[[Glossary#StaticType|static type]], the comparison is obviously allowed.
Bien entendu, et c'est le cas le plus commun, quand les deux expressions sont de même
 
  +
It is always possible to compare two expressions with the same static type using the comparison operators [[#EqNeq|<tt>=</tt> and <tt>/=</tt>]].
[[Glossary#StaticType|type statique]], la comparaison est évidemment autorisée.
 
Il est toujours possible de comparer deux expressions ayant le même type statique
 
à l'aide des opérateurs de comparaison [[#EqNeq|<tt>=</tt> et <tt>/=</tt>]].
 
   
  +
As soon as the two expressioms are not of the same static type, they have to conform to the following general rule.
Dès que les deux expressions ne sont pas de même type statique, il faut respecter la règle
 
  +
A comparison of the form:
générale suivante.
 
Une comparaison de la forme&nbsp;:
 
 
x = y
 
x = y
  +
or again:
ou encore&nbsp;:
 
 
x /= y
 
x /= y
est valide s'il est possible de faire une affectation dans le sens
+
is valid if it is possible to assign <tt>x</tt> to <tt>y</tt> or vice versa.
  +
More precisely, seeing that <tt>x</tt> and <tt>y</tt> are not necessarily variables, either the static type of <tt>x</tt> is acceptable for an assignment to the static type of <tt>y</tt> or else the static type of <tt>y</tt> is acceptable for an assignment to the static type of <tt>x</tt>.
<tt>x</tt> vers <tt>y</tt> ou bien,
 
s'il est possible de faire une affectation dans le sens
 
<tt>y</tt> vers <tt>x</tt>.
 
Plus exactement, car <tt>x</tt> et <tt>y</tt> ne sont pas forcément des variables,
 
soit le type statique de <tt>x</tt> est acceptable pour une affectation vers
 
le type statique de <tt>y</tt>
 
soit le type statique de <tt>y</tt> est acceptable pour une affectation vers
 
le type statique de <tt>x</tt>.
 
   
  +
The foregoing validity rule helps to check whether the program designer is writing a ''reasonable comparison''.
La règle de validité précédente contribue à vérifier que le concepteur du programme
 
  +
From experience, we have established that this rule, guided by common sense, is crucial for detecting upstream(?) errors, which are often careless mistakes.
effectue bien une ''comparaison raisonable''.
 
Par l'expérience, nous avons constaté que cette règle guidée par le bon sens est
 
cruciale pour la détection en amont d'erreurs qui sont souvent des erreurs
 
d'inattention.
 
   
  +
In a few ''uncommon and often obscure'' cases, this rule can perhaps be found to be too restrictive.
Dans quelques cas ''rares et souvent obscures'' cette règle peut être jugée trop
 
  +
If you should find yourself in the situation where the compiler is rejecting your comparison but nevertheless you genuinely think that comparing the two expressions is desirable, you can achieve your ends by using two local variables with a more general static type (ultimately, you can use type [[library_class:ANY|<tt>ANY</tt>]]).
contraignante.
 
Si d'aventure vous vous trouvez face à une situation ou le compilateur rejette votre
 
comparaison mais que vous pensez vraiment qu'il est souhaitable de comparer malgré tout
 
les deux expressions vous pourrez facilement arrivez à vos fins en utilisant deux
 
variables locales dont le type statique est plus général (utilisez éventuellement
 
pour cela le type [[library_class:ANY|<tt>ANY</tt>]]).
 
   
  +
To conclude this section on the general verification rule for comparisons with the
Pour terminer sur cette règle générale de vérification des comparaisons avec les opérateurs
 
[[#EqNeq|<tt>=</tt> et <tt>/=</tt>]], notons que la règle qui vient d'être énoncée englobe
+
[[#EqNeq|<tt>=</tt> and <tt>/=</tt>]] operators,
  +
let us remember that the rule just declared above also includes the [[#ExceptionalRules|previously mentioned case]] concerning the comparison of two expressions of type <tt>INTEGER_*</tt> or of type <tt>REAL_*</tt>.
aussi [[#ExceptionalRules|le cas évoqué précédemment]] qui concerne
 
  +
For example, it is possible to assign an expression of type
la comparaison des expressions de type <tt>INTEGER_*</tt> ou <tt>REAL_*</tt>.
 
  +
[[library_class:INTEGER_16|<tt>INTEGER_16</tt>]] to a variable of type [[library_class:INTEGER_32|<tt>INTEGER_32</tt>]].
En effet, il est par exemple possible d'affecter une expression de type
 
  +
A comparison between an expression of type
[[library_class:INTEGER_16|<tt>INTEGER_16</tt>]] dans une variable de
 
type [[library_class:INTEGER_32|<tt>INTEGER_32</tt>]].
+
[[library_class:INTEGER_16|<tt>INTEGER_16</tt>]] and one of type
  +
[[library_class:INTEGER_32|<tt>INTEGER_32</tt>]] is therefore valid.
La comparaison entre une expression de type
 
[[library_class:INTEGER_16|<tt>INTEGER_16</tt>]] avec une expression de type
 
[[library_class:INTEGER_32|<tt>INTEGER_32</tt>]] est donc valide.
 
   
 
<div id="is_deep_equal">
 
<div id="is_deep_equal">
   
==Comparaison avec <tt>is_deep_equal</tt>, la méthode figée==
+
==Comparison using <tt>is_deep_equal</tt>, the fixed method==
 
</div>
 
</div>
  +
Just for the sake of completeness in this section on comparing objects, let us finally be aware that the [[library_class:ANY|<tt>ANY</tt>]] class offers the possibility of a deep comparison of two objects: the <tt>is_deep_equal</tt> function.
Seulement pour être exhaustif dans cette partie consacrée à la comparaison
 
  +
The attributes of the two obects to be compared are themselves compared with the <tt>is_deep_equal</tt> function and so on recursively.
d'objets, signalons enfin que la classe
 
  +
This function recursively checks that the two objects are structurally identical.
[[library_class:ANY|<tt>ANY</tt>]] offre une possibilité de comparaison
 
  +
Note that it is sometimes possible to have an endless loop in the object graph(?), for example when comparing two linked lists with a circular structure.
en profondeur de deux objets&nbsp;: la fonction <tt>is_deep_equal</tt>.
 
  +
In order to be considered identical by <tt>is_deep_equal</tt>, it must be possible to superimpose the graph of the two objects.
Les attributs des deux objets à comparer sont eux mêmes comparés deux à
 
deux avec la fonction <tt>is_deep_equal</tt> et ainsi de suite récursivement.
 
Cette fonction compare récursivement que les deux objets sont structurellement
 
identiques.
 
Notons qu'une circularité dans le graphe des objets est parfois possible comme
 
par exemple dans le cas de la comparaison de deux listes chaînées structurellement
 
circulaires.
 
Pour être considérés identiques au sens <tt>is_deep_equal</tt>, le graphe des deux objets
 
à comparer doit être superposable.
 
   
La définition de la fonction <tt>is_deep_equal</tt> est figée (<tt>frozen</tt>) dans la
+
The definition of the <tt>is_deep_equal</tt> function is fixed in the [[library_class:ANY|<tt>ANY</tt>]] class.
  +
The <tt>is_deep_equal</tt> function must be used with care because it depends entirely on the implementation of the objects which it is comparing.
classe [[library_class:ANY|<tt>ANY</tt>]].
 
  +
In fact, as a general rule, it is always preferable to avoid using the <tt>is_deep_equal</tt> function.
La fonction <tt>is_deep_equal</tt> doit être utilisée avec précautions car elle
 
dépend totalement de l'implantation des objets qu'elle compare.
 
En fait, abstraction oblige, il est toujours préférable de ne pas utiliser la
 
fonction <tt>is_deep_equal</tt>.
 

Revision as of 23:30, 7 November 2005


*** Translation completed 7 Nov 2005
*** Will a French speaker please review to ensure that the
*** sense has been properly conveyed.

Comparing two objects

How to compare two objects is a problem that has to be mastered fairly quickly, for it is a very common problem posed in many, if not almost all applications.

In Eiffel, as in most object-oriented languages, there are several ways to compare two objects, whether by comparing their respective addresses or by comparing 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 is not possible to compare just any type of expression with any other type of expression. The validity of a comparison will be explained in detail below.

Finally, just for completeness, this part will also discuss the possibility of carrying out a third sort of comparison, deep equality, a comparison reserved only 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

The = operator is the fundamental 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 in the compiler's internals.

When comparing two expressions of a reference type with the = or /= operators, as has already been mentioned above, only the two addresses are compared. Now let us 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 which we will go into later, a comparison is accepted only when the two expanded types are exactly the same. It is 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. In the latter case, it is up to the program's designer to make a call to the appropriate conversion routine. There are multiple possibilities, perhaps to convert the INTEGER expression into a CHARACTER, or, contrariwise, to convert the CHARACTER expression into an INTEGER. In both cases there are many possible conversion routines. 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 completely impossible to automate the choice of conversion that should be applied before the comparison. It depends entirely 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, to allow the comparison of two expanded objects only 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. That being said, two exceptions to this very strict rule remain, probably for historical reasons.

The two exceptions concern two special cases that leave no doubt thanks to the fact that the expanded objects concerned are very elementary. The first exception to the strict rule, requiring two expanded types to be identical, 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 is 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.

Note, finally, that it is forbidden to compare directly any object from the REAL_* family with any object from the INTEGER_* family. If you find yourself having to make such a comparison, which is not illogical, it is 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.

The redefinable is_equal routine

The redefinable is_equal routine provides a more sophisticated comparison than using the predefined = operator. Consider once again our example of comparing two variables of type POINT, but this time let us use is_equal. As in the case above, let us suppose for the moment that the two variables point_a and point_b are initialised with something other than Void:

if point_a.is_equal(point_b) then
   io.put_string("Either they are both the same POINT or else they are 2 identical POINTs.")
else
   io.put_string("They are two different POINTs.")
end

In a given class, if the is_equal routine has not been redefined by the user, the behaviour of is_equal is to compare all attributes of the two objects with each other. That means, for the POINT example, successively comparing the two x attributes and then the two y attributes. In other words, it is as if we had written:

if (point_a.x = point_b.x) and (point_a.y = point_b.y) then
   io.put_string("Either they are both the same POINT or else they are 2 identical POINTs.")
else
   io.put_string("They are two different POINTs.")
end

See how the comparison between attributes does not use is_equal, but the basic fixed = operator. The following diagram presents a picture of the possible memory contents during comparison of point_a and point_b. Note that in the following case, these two POINTs situated in two memory locations are identical and, consequently, comparison using is_equal will return True:

is_equal with some POINTs

In the example above, the two objects that are being compared are both instances of the POINT class: very simple objects. As the diagram shows, a POINT is composed of two expanded attributes, or, more precisely, the attributes x and y of type REAL, a primitive expanded type (i.e. without an intermediate pointer). In this case the default definition of the is_equal function fits perfectly.

Before describing is_equal any further, let us make it clear that is_equal can be used when the type of the two compared objects is expanded. For example, with INTEGERs, the result of a comparison using the predefined = operator has exactly the same effect as using the is_equal routine. Note however that using is_equal is quite unusual when the two operands are expanded, especially if it is a basic expanded type like INTEGER, CHARACTER, REAL or BOOLEAN. In general, by convention and especially for the sake of simplicity, = and /= are preferred for primitive expanded types.

As soon as the objects are more complex, with for example some attributes that themselves point towards other objects, it is necessary to pay attention to the predefined behaviour of the is_equal function. Consider for example the case of comparing two TRIANGLEs, as in the following diagram:

is_equal with some TRIANGLEs

Both objects of class TRIANGLE in the diagram above are identical but if one proceeds to compare them using the predefined is_equal routine the result will be False! In fact, let us emphasise that triangle_a and triangle_b designate, even if they are similar, two distinct objects in memory. Indeed, if the is_equal function has not been redefined in the TRIANGLE class, the comparison of triangle_a and triangle_b using is_equal, in other words the expression:

triangle_a.is_equal(triangle_b)

is equivalent to the expression:

(triangle_a.p1 = triangle_b.p1) and (triangle_a.p2 = triangle_b.p2) and (triangle_a.p3 = triangle_b.p3)

The expression above corresponds to the predefined behaviour of is_equal. In order to obtain a more useful comparison function, it is up to the designer of the TRIANGLE class to think of how to alter the predefined definition by redefining TRIANGLE's is_equal function like this:

is_equal (other: TRIANGLE): BOOLEAN is
   do
      Result := p1.is_equal(other.p1) and p2.is_equal(other.p2) and p3.is_equal(other.p3)
   end

It is very hard to imagine how a good function for comparison could be defined automatically. In particular, it is not always enough to call is_equal again on the attributes, as in the TRIANGLE case, rather than using =. (To convince youself of this, see the STRING and ARRAY classes as examples.) Thus, since we do not know how to automate this task with a language rule or by adding a strong dose of intelligence to the compiler, the job belongs solely to the designer of the class. The conscientious class designer must ensure that the is_equal function behaves properly for objects of that class. The default behaviour described above has been chosen for its simplicity, because it is is for the most part suitable as a good comparison function and also because it is implemented easily in low level machine instructions that are very fast (comparison instructions for two memory blocks).

Comparison validity

Do not look for the definition of the = and /= comparison operators in the Eiffel classes' source code, because these two operators are predefined and their definition is deliberately fixed in the compiler's internals. Since one of the goals of the Eiffel language is to limit the possibility of careless errors, it would not be reasonable to allow just any comparison (that is, to allow all mixtures, like comparing a LORRY with a FLOWER, to take an extreme example). The static types of two expressions to be compared must conform to certain constraints. Of course -- and this is the usual case -- when the two expressions are of the same static type, the comparison is obviously allowed. It is always possible to compare two expressions with the same static type using the comparison operators = and /=.

As soon as the two expressioms are not of the same static type, they have to conform to the following general rule. A comparison of the form:

x = y

or again:

x /= y

is valid if it is possible to assign x to y or vice versa. More precisely, seeing that x and y are not necessarily variables, either the static type of x is acceptable for an assignment to the static type of y or else the static type of y is acceptable for an assignment to the static type of x.

The foregoing validity rule helps to check whether the program designer is writing a reasonable comparison. From experience, we have established that this rule, guided by common sense, is crucial for detecting upstream(?) errors, which are often careless mistakes.

In a few uncommon and often obscure cases, this rule can perhaps be found to be too restrictive. If you should find yourself in the situation where the compiler is rejecting your comparison but nevertheless you genuinely think that comparing the two expressions is desirable, you can achieve your ends by using two local variables with a more general static type (ultimately, you can use type ANY).

To conclude this section on the general verification rule for comparisons with the = and /= operators, let us remember that the rule just declared above also includes the previously mentioned case concerning the comparison of two expressions of type INTEGER_* or of type REAL_*. For example, it is possible to assign an expression of type INTEGER_16 to a variable of type INTEGER_32. A comparison between an expression of type INTEGER_16 and one of type INTEGER_32 is therefore valid.

Comparison using is_deep_equal, the fixed method

Just for the sake of completeness in this section on comparing objects, let us finally be aware that the ANY class offers the possibility of a deep comparison of two objects: the is_deep_equal function. The attributes of the two obects to be compared are themselves compared with the is_deep_equal function and so on recursively. This function recursively checks that the two objects are structurally identical. Note that it is sometimes possible to have an endless loop in the object graph(?), for example when comparing two linked lists with a circular structure. In order to be considered identical by is_deep_equal, it must be possible to superimpose the graph of the two objects.

The definition of the is_deep_equal function is fixed in the ANY class. The is_deep_equal function must be used with care because it depends entirely on the implementation of the objects which it is comparing. In fact, as a general rule, it is always preferable to avoid using the is_deep_equal function.