|
|
|
Karsten Heusser <karsten.heusser@gmx.de>
|
Dear Eiffel Enthusiasts,
I encountered a problem that I am unable to solve without help. Assume you would like to have arrays of integers that are comparable to each other. For simplification assume further that the arrays are compared based on their first element, i.e. the element at index `lower', if the arrays are not empty. <Implementation of COMPARABLE_INTEGER_ARRAY> class COMPARABLE_INTEGER_ARRAY inherit ARRAY [INTEGER] undefine is_equal redefine make end COMPARABLE undefine copy redefine is_less end create make feature -- Initialization make (lo, up: INTEGER) do Precursor (lo, up) print ("COMPARABLE_INTEGER_ARRAY.make has been called.%N") end feature -- Comparison is_less alias "<" (other: like Current): BOOLEAN require else current_not_empty: not is_empty other_not_empty: not other.is_empty do print ("COMPARABLE_INTEGER_ARRAY.is_less has been called.%N") print ("---------------- `Current' ----------------%N") print (Current) print ("---------------- `other' ------------------%N") print (other) print ("-------------------------------------------%N") if item (lower) < other.item (other.lower) then Result := true end end feature -- Test test do print ("COMPARABLE_INTEGER_ARRAY.test has been called.%N") end end </Implementation of COMPARABLE_INTEGER_ARRAY> The root class of the system COMPARABLE_TEST reads <Implementation of COMPARABLE_TEST> class COMPARABLE_TEST create make feature make do print ("COMPARABLE_TEST.make has been called.%N") create comparable_integer_array.make (1, 3) print ("COMPARABLE_TEST.make has created the array.%N") end comparable_integer_array: COMPARABLE_INTEGER_ARRAY end </Implementation of COMPARABLE_TEST> Running the example yields the following console output: <Output> COMPARABLE_TEST.make has been called. COMPARABLE_INTEGER_ARRAY.make has been called. COMPARABLE_INTEGER_ARRAY.is_less has been called. ---------------- `Current' ---------------- COMPARABLE_INTEGER_ARRAY [0x15A0618] area: SPECIAL [INTEGER_32] [0x15A063C] -- begin special object -- 0: INTEGER_32 = 0 1: INTEGER_32 = 0 2: INTEGER_32 = 0 -- end special object -- object_comparison: BOOLEAN = False lower: INTEGER_32 = 1 upper: INTEGER_32 = 3 ---------------- `other' ------------------ COMPARABLE_INTEGER_ARRAY [0x15A0618] area: SPECIAL [INTEGER_32] [0x15A063C] -- begin special object -- 0: INTEGER_32 = 0 1: INTEGER_32 = 0 2: INTEGER_32 = 0 -- end special object -- object_comparison: BOOLEAN = False lower: INTEGER_32 = 1 upper: INTEGER_32 = 3 ------------------------------------------- COMPARABLE_TEST.make has created the array. Press Return to finish the execution... </Output> As you can see, `is_less' but not `test' has been called immediately after creation of the comparable array. My question is: Who is the caller? As far as I can see this call should not happen. The printout shows, that the comparable array (`Current') is compared with itself (`other'). The problem gets even worse, if the comparable array is empty after creation as with create comparable_integer_array.make (1, 0). Then, the precondition of array's `item' is violated. (The precondition of `is_less' does not catch the violation of `xxx_not_empty' because of require else.) The behavior is not specific for comparable arrays. It is similar with COMPARABLE_INTEGER_LISTs. What am I missing? Thank you very much for your help. (EiffelStudio 6.4.7.9451 Enterprise Edition, Windows XP) Kind regards, Karsten |
||||||||||||||||
|
Colin LeMahieu
|
Class invariants are evaluated before and after every feature invocation on
a class with the exception of creation procedures where it's only checked after the feature finishes. You're probably seeing the class invariants of COMPARABLE being evaluated. On Fri, Oct 2, 2009 at 2:22 PM, karstenheusser <[hidden email]>wrote: > > > Dear Eiffel Enthusiasts, > > I encountered a problem > that I am unable to solve without help. > Assume you would like to have arrays of integers > that are comparable to each other. > For simplification assume further > that the arrays are compared based on their first element, > i.e. the element at index `lower', > if the arrays are not empty. > > <Implementation of COMPARABLE_INTEGER_ARRAY> > class > COMPARABLE_INTEGER_ARRAY > > inherit > ARRAY [INTEGER] > undefine > is_equal > redefine > make > end > > COMPARABLE > undefine > copy > redefine > is_less > end > > create > make > > feature -- Initialization > > make (lo, up: INTEGER) > do > Precursor (lo, up) > print ("COMPARABLE_INTEGER_ARRAY.make has been called.%N") > end > > feature -- Comparison > > is_less alias "<" (other: like Current): BOOLEAN > require else > current_not_empty: not is_empty > other_not_empty: not other.is_empty > do > print ("COMPARABLE_INTEGER_ARRAY.is_less has been called.%N") > print ("---------------- `Current' ----------------%N") > print (Current) > print ("---------------- `other' ------------------%N") > print (other) > print ("-------------------------------------------%N") > if > item (lower) < other.item (other.lower) > then > Result := true > end > end > > feature -- Test > > test > do > print ("COMPARABLE_INTEGER_ARRAY.test has been called.%N") > end > > end > </Implementation of COMPARABLE_INTEGER_ARRAY> > > The root class of the system COMPARABLE_TEST reads > > <Implementation of COMPARABLE_TEST> > class > COMPARABLE_TEST > > create > make > > feature > > make > do > print ("COMPARABLE_TEST.make has been called.%N") > create comparable_integer_array.make (1, 3) > print ("COMPARABLE_TEST.make has created the array.%N") > end > > comparable_integer_array: COMPARABLE_INTEGER_ARRAY > end > </Implementation of COMPARABLE_TEST> > > Running the example yields the following console output: > > <Output> > COMPARABLE_TEST.make has been called. > COMPARABLE_INTEGER_ARRAY.make has been called. > COMPARABLE_INTEGER_ARRAY.is_less has been called. > ---------------- `Current' ---------------- > COMPARABLE_INTEGER_ARRAY [0x15A0618] > area: SPECIAL [INTEGER_32] [0x15A063C] > -- begin special object -- > 0: INTEGER_32 = 0 > 1: INTEGER_32 = 0 > 2: INTEGER_32 = 0 > -- end special object -- > object_comparison: BOOLEAN = False > lower: INTEGER_32 = 1 > upper: INTEGER_32 = 3 > ---------------- `other' ------------------ > COMPARABLE_INTEGER_ARRAY [0x15A0618] > area: SPECIAL [INTEGER_32] [0x15A063C] > -- begin special object -- > 0: INTEGER_32 = 0 > 1: INTEGER_32 = 0 > 2: INTEGER_32 = 0 > -- end special object -- > object_comparison: BOOLEAN = False > lower: INTEGER_32 = 1 > upper: INTEGER_32 = 3 > ------------------------------------------- > COMPARABLE_TEST.make has created the array. > > Press Return to finish the execution... > </Output> > > As you can see, `is_less' but not `test' has been called > immediately after creation of the comparable array. > My question is: Who is the caller? > As far as I can see this call should not happen. > The printout shows, that the comparable array (`Current') > is compared with itself (`other'). > > The problem gets even worse, if the comparable array > is empty after creation as with > create comparable_integer_array.make (1, 0). > Then, the precondition of array's `item' is violated. > (The precondition of `is_less' does not catch the violation > of `xxx_not_empty' because of require else.) > > The behavior is not specific for comparable arrays. > It is similar with COMPARABLE_INTEGER_LISTs. > > What am I missing? > Thank you very much for your help. > (EiffelStudio 6.4.7.9451 Enterprise Edition, Windows XP) > > Kind regards, > Karsten > > > [Non-text portions of this message have been removed] |
||||||||||||||||
|
Jimmy Johnson
|
In reply to this post
by Karsten Heusser
The invariant from COMPARABLE is being called after creation. From COMPARABLE:
invariant irreflexive_comparison: not (Current < Current) Jimmy J. Johnson --- In [hidden email], "karstenheusser" <karsten.heusser@...> wrote: > > Dear Eiffel Enthusiasts, > > I encountered a problem > that I am unable to solve without help. > Assume you would like to have arrays of integers > that are comparable to each other. > For simplification assume further > that the arrays are compared based on their first element, > i.e. the element at index `lower', > if the arrays are not empty. > > <Implementation of COMPARABLE_INTEGER_ARRAY> > class > COMPARABLE_INTEGER_ARRAY > > inherit > ARRAY [INTEGER] > undefine > is_equal > redefine > make > end > > COMPARABLE > undefine > copy > redefine > is_less > end > > create > make > > feature -- Initialization > > make (lo, up: INTEGER) > do > Precursor (lo, up) > print ("COMPARABLE_INTEGER_ARRAY.make has been called.%N") > end > > feature -- Comparison > > is_less alias "<" (other: like Current): BOOLEAN > require else > current_not_empty: not is_empty > other_not_empty: not other.is_empty > do > print ("COMPARABLE_INTEGER_ARRAY.is_less has been called.%N") > print ("---------------- `Current' ----------------%N") > print (Current) > print ("---------------- `other' ------------------%N") > print (other) > print ("-------------------------------------------%N") > if > item (lower) < other.item (other.lower) > then > Result := true > end > end > > feature -- Test > > test > do > print ("COMPARABLE_INTEGER_ARRAY.test has been called.%N") > end > > end > </Implementation of COMPARABLE_INTEGER_ARRAY> > > The root class of the system COMPARABLE_TEST reads > > <Implementation of COMPARABLE_TEST> > class > COMPARABLE_TEST > > create > make > > feature > > make > do > print ("COMPARABLE_TEST.make has been called.%N") > create comparable_integer_array.make (1, 3) > print ("COMPARABLE_TEST.make has created the array.%N") > end > > comparable_integer_array: COMPARABLE_INTEGER_ARRAY > end > </Implementation of COMPARABLE_TEST> > > Running the example yields the following console output: > > <Output> > COMPARABLE_TEST.make has been called. > COMPARABLE_INTEGER_ARRAY.make has been called. > COMPARABLE_INTEGER_ARRAY.is_less has been called. > ---------------- `Current' ---------------- > COMPARABLE_INTEGER_ARRAY [0x15A0618] > area: SPECIAL [INTEGER_32] [0x15A063C] > -- begin special object -- > 0: INTEGER_32 = 0 > 1: INTEGER_32 = 0 > 2: INTEGER_32 = 0 > -- end special object -- > object_comparison: BOOLEAN = False > lower: INTEGER_32 = 1 > upper: INTEGER_32 = 3 > ---------------- `other' ------------------ > COMPARABLE_INTEGER_ARRAY [0x15A0618] > area: SPECIAL [INTEGER_32] [0x15A063C] > -- begin special object -- > 0: INTEGER_32 = 0 > 1: INTEGER_32 = 0 > 2: INTEGER_32 = 0 > -- end special object -- > object_comparison: BOOLEAN = False > lower: INTEGER_32 = 1 > upper: INTEGER_32 = 3 > ------------------------------------------- > COMPARABLE_TEST.make has created the array. > > Press Return to finish the execution... > </Output> > > As you can see, `is_less' but not `test' has been called > immediately after creation of the comparable array. > My question is: Who is the caller? > As far as I can see this call should not happen. > The printout shows, that the comparable array (`Current') > is compared with itself (`other'). > > The problem gets even worse, if the comparable array > is empty after creation as with > create comparable_integer_array.make (1, 0). > Then, the precondition of array's `item' is violated. > (The precondition of `is_less' does not catch the violation > of `xxx_not_empty' because of require else.) > > The behavior is not specific for comparable arrays. > It is similar with COMPARABLE_INTEGER_LISTs. > > What am I missing? > Thank you very much for your help. > (EiffelStudio 6.4.7.9451 Enterprise Edition, Windows XP) > > Kind regards, > Karsten > |
||||||||||||||||
|
Jimmy Johnson
|
In reply to this post
by Karsten Heusser
In `is_less' you could:
if is_empty and other.is_empty then -- the two arrays are equal Result := False elseif is_empty and not other.is_empty then -- an empty array is less than one that is not empty Result := True elseif not is_empty and other.is_empty the -- other way around Result := False else check both_arrays_not_empty: not is_empty and not other.is_empty end Result := item (1) < other.item (1) end This may really be logical for an array but I think it will get you around the postcondition of `is_less', "asymmetric: Result implies not (other < Current)", and not call `item' on an empty array. Maybe that will help.? --- In [hidden email], "karstenheusser" <karsten.heusser@...> wrote: > > The problem gets even worse, if the comparable array > is empty after creation as with > create comparable_integer_array.make (1, 0). > Then, the precondition of array's `item' is violated. > (The precondition of `is_less' does not catch the violation > of `xxx_not_empty' because of require else.) > |
||||||||||||||||
|
Karsten Heusser <karsten.heusser@gmx.de>
|
In reply to this post
by Karsten Heusser
Dear Colin & Jimmy,
thank you very much for taking away my blinders ;-) Indeed, the `troublemaker' was the invariant of COMPARABLE: irreflexive_comparison: not (Current < Current). Jimmy suggested to work around the problem by specially treating empty arrays. For the time being, I will follow his advice. However, I have the impression that this solution is not really appropriate. Comparable objects may be in a state where comparisons are not possible or conceptually useless as was the case in my previous example when the comparable array was empty. In my opinion, it would be useful to have a status reporting function in COMPARABLE for redefinition in heirs: is_in_comparable_state: BOOLEAN -- Is comparison possible in Current's present state? do Result := True end (The name `is_comparable' might be less appropriate because inheriting from COMPARABLE suggests that `is_comparable' is always true.) The invariant of COMPARABLE may then read: irreflexive_comparison: is_in_comparable_state implies (not (Current < Current)). The preconditions of (PART_)COMPARABLE.is_less and its companions may then read: other_exists: other /= Void valid_states: Current.is_in_comparable_state and other.is_in_comparable_state Having the function `is_in_comparable_state' and the above mentioned preconditions might yield the following advantages: 1) The invariant's irreflexive comparison would take place only if the object is in an appropriate state preventing trouble of the kind that I have. 2) Heirs of COMPARABLE had increased flexibility in tailoring the preconditions of `is_less' and companions. At the moment, extensions of the preconditions have to use `require else' which is not appropriate to forbid comparisons if the object is in a non-comparable state. What do you think? Thank you very much. Kind regards, Karsten |
|
dlebansais
|
> Jimmy suggested to work around the problem
> by specially treating empty arrays. > For the time being, I will follow his advice. > However, I have the impression that this solution > is not really appropriate. > > Comparable objects may be in a state where > comparisons are not possible or conceptually useless > as was the case in my previous example > when the comparable array was empty. > > In my opinion, it would be useful to have > a status reporting function in COMPARABLE > for redefinition in heirs: > > is_in_comparable_state: BOOLEAN > -- Is comparison possible in Current's present state? > do > Result := True > end > > (The name `is_comparable' might be less appropriate > because inheriting from COMPARABLE suggests > that `is_comparable' is always true.) > > The invariant of COMPARABLE may then read: > > irreflexive_comparison: > is_in_comparable_state implies (not (Current < Current)). > > The preconditions of (PART_)COMPARABLE.is_less > and its companions may then read: > > other_exists: other /= Void > valid_states: Current.is_in_comparable_state and > other.is_in_comparable_state > > Having the function `is_in_comparable_state' > and the above mentioned preconditions > might yield the following advantages: > > 1) The invariant's irreflexive comparison would take place > only if the object is in an appropriate state > preventing trouble of the kind that I have. > > 2) Heirs of COMPARABLE had increased flexibility > in tailoring the preconditions of `is_less' and companions. > At the moment, extensions of the preconditions > have to use `require else' which is not appropriate > to forbid comparisons if the object > is in a non-comparable state. > > What do you think? > I'm a bit scared by objects deriving from COMPARABLE that can't always be compared. What if two objects have is_comparable false and you want to compare them? You could instead require that arrays can't be empty. David Le Bansais |
||||||||||||||||
|
helmut.brandl
|
If not all objects are comparable, I would prefer to use PART_COMPARABLE
instead of COMPARABLE. PART_COMPARABLE models a partial order instead of a total order. Regards Helmut David wrote: >> Jimmy suggested to work around the problem >> by specially treating empty arrays. >> For the time being, I will follow his advice. >> However, I have the impression that this solution >> is not really appropriate. >> >> Comparable objects may be in a state where >> comparisons are not possible or conceptually useless >> as was the case in my previous example >> when the comparable array was empty. >> >> In my opinion, it would be useful to have >> a status reporting function in COMPARABLE >> for redefinition in heirs: >> >> is_in_comparable_state: BOOLEAN >> -- Is comparison possible in Current's present state? >> do >> Result := True >> end >> >> (The name `is_comparable' might be less appropriate >> because inheriting from COMPARABLE suggests >> that `is_comparable' is always true.) >> >> The invariant of COMPARABLE may then read: >> >> irreflexive_comparison: >> is_in_comparable_state implies (not (Current < Current)). >> >> The preconditions of (PART_)COMPARABLE.is_less >> and its companions may then read: >> >> other_exists: other /= Void >> valid_states: Current.is_in_comparable_state and >> other.is_in_comparable_state >> >> Having the function `is_in_comparable_state' >> and the above mentioned preconditions >> might yield the following advantages: >> >> 1) The invariant's irreflexive comparison would take place >> only if the object is in an appropriate state >> preventing trouble of the kind that I have. >> >> 2) Heirs of COMPARABLE had increased flexibility >> in tailoring the preconditions of `is_less' and companions. >> At the moment, extensions of the preconditions >> have to use `require else' which is not appropriate >> to forbid comparisons if the object >> is in a non-comparable state. >> >> What do you think? >> > > I'm a bit scared by objects deriving from COMPARABLE that can't always be compared. What if two objects have is_comparable false and you want to compare them? > > You could instead require that arrays can't be empty. > > David Le Bansais > > > > ------------------------------------ > > Yahoo! Groups Links > > > |
||||||||||||||||
|
Karsten Heusser <karsten.heusser@gmx.de>
|
Dear friends,
Thanks for your responses. Helmut, I have to sort the arrays, hence, inheriting from PART_COMPARABLE is not an option. Furthermore, before executing the command SORTED_TWO_WAY_LIST.extend (array) my code ensures that `array' is in a comparable state. Exactly speaking, immediately after creation all the arrays are not comparable. However, most of them become comparable later on and only these arrays are put in the sorted list. David, I would like to give an example: Assume you are a maths teacher and you want to implement an application that manages your pupils and all their grades (marks). The PUPIL class may have the attributes `first_name' and `surname' for identification and `grades' for storing the grades. To simplify matters, `grades' could be an ARRAY [INTEGER]. Furthermore, your application has to have the query ordered: SORTED_TWO_WAY_LIST [PUPIL] enabling you to keep track of the performance ranking of your pupils from time to time. Hence, PUPIL has to inherit from COMPARABLE. In my opinion the list should contain only pupils who already have got grades so far because it is legal to consider the others not to be comparable at the moment. At the beginning of a school year when creating PUPIL objects none of them is in a comparable state. For me it seems natural (1) to have PUPIL inherit from COMPARABLE (2) to allow a pupil's `grades' array to be void or empty implying a non-comparable state of that pupil (3) to protect `is_less' from being called if (2) holds. As far as I can see, the current implementation of COMPARABLE makes it difficult to model such a scenario properly. Jimmy's proposal (15560, Oct 6, 2009, 3:02 am) constitutes a helpful workaround (thanks again, Jimmy) but not a clean solution. My feeling is that the underlying problem is related to the following facts: (1) Preconditions can be made `abstract' by using Boolean functions, which -- on a `concrete' level -- can be weakened (contravariance) or strengthened (covariance) in redefining heirs. (2) Unfortunately, the majority of preconditions is coded in `manifest' terms, eg other_exists: other /= Void, which can only be weakened by heirs (require else, contravariance). Thus, depending on the coding scheme used heirs have more (1) or less (2) flexibility in tailoring preconditions. Personally, I do not like this mismatch. These topics have been addressed earlier. If you are interested in reading them search for `Assertions with Inheritance and Polymorphism' and `Anchored covariance evilness'. Regards, Karsten |
||||||||||||||||
|
helmut.brandl
|
Hello Karsten,
as far as I understand, you have the following conceptual problem: 1. You want PUPILs to be inserted in some sorted list, therefore you want them be COMPARABLE. 2. However initially the PUPILs are not yet comparable. As long as they don't have grades your PUPILs are not yet comparable. 3. Your "comparability" is a status which changes during runtime. 4. Eiffel inheritance is a static property. If a class A inherits from class B, the inheritance relation is a static (i.e. compile time) relation which cannot change during runtime. I see some clean solutions to the problem. 1. Make all PUPILs "comparable". E.g. give PUPILs with no grades the lowest rank. If you achieve that, the class PUPIL can inherit from COMPARABLE, because it is now really comparable. 2. Stick to your approach that PUPILs are initially not "comparable". In that case PUPIL cannot inherit from COMPARABLE, because it is *not* COMPARABLE. Then create a class COMPARABLE_PUPIL which inherits from COMPARABLE and has an attribute `pupil' which refers to the original PUPIL. 3. A variation of 2: Make COMPARABLE_PUPIL a heir of COMPARABLE and PUPIL and give PUPIL a query `comparable: COMPARABLE_PUPIL which creates the corresponding COMPARABLE, if the PUPIL has already grades. All options has pros and cons. If possible, I would prefer the first one, because it is the simplest one. Solution 2 has the disadvantage that you have to write features for COMPARABLE_PUPILE which simply call the corresponding feature of PUPIL. Solution 3 has the disadvantage that you create for each PUPIL a copy, as soon it is "comparable". Anyhow. You cannot convert inheritance relations to some kind of runtime property. Regards Helmut Karsten wrote: > Dear friends, > > Thanks for your responses. > > Helmut, I have to sort the arrays, hence, > inheriting from PART_COMPARABLE is not an option. > Furthermore, before executing the command > SORTED_TWO_WAY_LIST.extend (array) > my code ensures that `array' is in a comparable state. > Exactly speaking, immediately after creation > all the arrays are not comparable. > However, most of them become comparable later on > and only these arrays are put in the sorted list. > > David, I would like to give an example: > Assume you are a maths teacher and you want > to implement an application that manages > your pupils and all their grades (marks). > The PUPIL class may have the attributes > `first_name' and `surname' for identification > and `grades' for storing the grades. > To simplify matters, > `grades' could be an ARRAY [INTEGER]. > > Furthermore, your application has to have the query > ordered: SORTED_TWO_WAY_LIST [PUPIL] > enabling you to keep track > of the performance ranking > of your pupils from time to time. > Hence, PUPIL has to inherit from COMPARABLE. > > In my opinion the list should contain only pupils > who already have got grades so far > because it is legal to consider the others > not to be comparable at the moment. > At the beginning of a school year > when creating PUPIL objects > none of them is in a comparable state. > > For me it seems natural > > (1) to have PUPIL inherit from COMPARABLE > > (2) to allow a pupil's `grades' array > to be void or empty > implying a non-comparable state of that pupil > > (3) to protect `is_less' from being called > if (2) holds. > > As far as I can see, > the current implementation of COMPARABLE > makes it difficult to model such a scenario properly. > Jimmy's proposal (15560, Oct 6, 2009, 3:02 am) > constitutes a helpful workaround (thanks again, > Jimmy) but not a clean solution. > > My feeling is that the underlying problem > is related to the following facts: > > (1) Preconditions can be made `abstract' > by using Boolean functions, > which -- on a `concrete' level -- can be > weakened (contravariance) > or strengthened (covariance) > in redefining heirs. > > (2) Unfortunately, the majority of preconditions > is coded in `manifest' terms, eg > other_exists: other /= Void, > which can only be weakened by heirs > (require else, contravariance). > > Thus, depending on the coding scheme used > heirs have more (1) or less (2) flexibility > in tailoring preconditions. > Personally, I do not like this mismatch. > > These topics have been addressed earlier. > If you are interested in reading them search for > `Assertions with Inheritance and Polymorphism' > and `Anchored covariance evilness'. > > Regards, > Karsten > > > > ------------------------------------ > > Yahoo! Groups Links > > > |
||||||||||||||||
|
Karsten Heusser <karsten.heusser@gmx.de>
|
Dear Helmut,
I appreciate your suggestions that have been drafted 11:49 Swiss time ;-) (If anyone else is reading this message who is interested in covariant redefinitions may read further. I would appreciate any comments.) Your first solution is similar to what Jimmy had suggested. Definitely, it would work. However, it is only a workaround, because it does not model the concept correctly. The concept is, that an object of type PUPIL may be in a state where it cannot be compared with other pupils, e.g. immediately after its creation. Your first suggestion `bends' this concept in order to fit in the concept of the designer of the class COMPARABLE. As to your second solution, it seems to postpone the problem by one level. If COMPARABLE_PUPIL has an attribute of type PUPIL then it also has to deal with the case that there may be still no grades to compare. Your third solution is very interesting. In my own words it reads: Equip (the non-comparable) PUPIL with a query returning a COMPARABLE_PUPIL only if it is in a comparable state, Void otherwise. This solution transforms the result type of `Is the pupil object in a comparable state?' from BOOLEAN into the type COMPARABLE_PUPIL that is either attached or Void. This solution models the concept almost correctly, although in a somewhat complicated way. In the following I give an example that would model the concept correctly. Assume, the designer of (PART_)COMPARABLE would have used an abstract precondition for `is_less': <version_with_abstract_precondition> is_less alias "<" (other: like Current): BOOLEAN ........-- Is current object less than `other'? ....require ........preconditions_met: are_preconditions_met (other) ....deferred ....end are_preconditions_met (other: like Current): BOOLEAN ....do ........Result := (other /= Void) ....end </version_with_abstract_precondition> Unfortunately, this is what you may find in EiffelBase: <version_with_manifest_precondition> is_less alias "<" (other: like Current): BOOLEAN ........-- Is current object less than `other'? ....require ........other_exists: other /= Void ....deferred ....end </version_with_manifest_precondition> Based on the version_with_abstract_precondition PUPIL could inherit from COMPARABLE and re/define the involved functions this way: <extract_from_class_pupil> is_less (other: like Current): BOOLEAN ........-- Are the pupil's grades worse ........-- than the grades of `other'? ....require ........preconditions_met: are_preconditions_met (other) ....do ........-- some implementation comparing ........-- the grades of `Current' and `other' ....end are_preconditions_met (other: like Current): BOOLEAN ....do ........Result := ................(other /= Void and then ................other.is_in_comparable_state) and ................Current.is_in_comparable_state ....end is_in_comparable_state: BOOLEAN ........-- Does the pupil have grades ........-- allowing for comparison with other pupils? ....do ............check ................grades_exist: grades /= Void ........................-- Duty of the creation procedure. ............end ........Result := grades.count > 0 ....end </extract_from_class_pupil> The last example shows that it is possible to model the concept (of not always being comparable) correctly. However, the designer of (PART_)COMPARABLE decided to hardwire the precondition of `is_less' using the version_with_manifest_precondition. He did not foresee that their might be a special heir demanding more prerequisites than simply an existent `other'. COMPARABLE_PUPIL was only meant to serve as an example for David (message #15562) who was `a bit scared by objects deriving from COMPARABLE that can't always be compared'. However, the problem is of a more general nature. If you accept that covariant redefinition is a good thing you will strengthen arguments once in a while implying some homework in order to prevent CAT calls (COT calls, as Bernd Schoeller suggested). Moreover, you are allowed to cheat the Assertion Redeclaration Rule, i.e. to strengthen a precondition, by means of an abstract precondition. Why then, is it not allowed to strengthen manifest preconditions this way: Caution: This is not valid Eiffel code, because of `require' instead of `require else'. <extract_with_strengthened_manifest_precondition> is_less alias "<" (other: like Current): BOOLEAN ........-- Are the pupil's grades worse ........-- than the grades of `other'? ....require ................(other_exists: other /= Void and then ................other_is_in_comparable state: ................other.is_in_comparable_state) and ................current_is_in_comparable_state: ................Current.is_in_comparable_state ....do ........-- some implementation comparing ........-- the grades of `Current' and `other' ....end </extract_with_strengthened_manifest_precondition> In analogy to CAT/COT call prevention one would have to avoid COP calls (change of precondition). From my (current) perspective, there is no conceptual difference between CAT/COT and COP call prevention. Extending the example above, an application specific client class, that triggers the sorting of the pupils would have to fulfill the strengthened precondition as given in the extract_from_class_pupil. Most likely, it would do so by `Cutting out the middleman' as described in OOSC2, p. 575, i.e. it would deal with COMPARABLE_PUPILs directly instead of going `through' COMPARABLE: <client_code_obeying_the_abstract_precondition> ordered: SORTED_TWO_WAY_LIST [PUPIL] ........-- Pupils arranged in ascending order ........-- according to their school achievements ........-- as measured by their grades. sort_pupils ........-- Generate `ordered'! ....local ........p: COMPARABLE_PUPIL ........-- some more locals here ....do ........create ordered.make ........-- some traversal over `pupils' here ........-- containing the COP call protection: ........p := pupils.item ........if p.is_in_comparable_state then ............ordered.extend (p) ........end ....end </client_code_obeying_the_abstract_precondition> Assume, that the above extract_with_strengthened_manifest_precondition would be valid Eiffel code, then the COP prevention in the client might read exactly the same: ........p := pupils.item ........if p.is_in_comparable_state then ............ordered.extend (p) ........end In summary, the possibility to strengthen arguments (by covariant redefinition) contrasts with the prohibition of strengthening the precondition (require else is enforced instead). Although there is the danger of CAT calls with covariant redefinition, it is allowed for practical reasons. By the same token, it should also be allowed to strengthen preconditions, even if the ancestors do not provide abstract preconditions. This would be a step towards broader realization of the open-closed principle. Regards, Karsten |
||||||||||||||||
| Free Embeddable Forum Powered by Nabble | Help |