COMPARABLE.is_less

10 messages Options
Embed this post
Permalink
Karsten Heusser <karsten.heusser@gmx.de>

COMPARABLE.is_less

Reply Threaded More More options
Print post
Permalink
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

Re: COMPARABLE.is_less

Reply Threaded More More options
Print post
Permalink
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

Re: COMPARABLE.is_less

Reply Threaded More More options
Print post
Permalink
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

Re: COMPARABLE.is_less

Reply Threaded More More options
Print post
Permalink
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>

Re: COMPARABLE.is_less

Reply Threaded More More options
Print post
Permalink
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

Re: COMPARABLE.is_less

Reply Threaded More More options
Print post
Permalink
> 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

Re: Re: COMPARABLE.is_less

Reply Threaded More More options
Print post
Permalink
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>

Re: COMPARABLE.is_less

Reply Threaded More More options
Print post
Permalink
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

Re: Re: COMPARABLE.is_less

Reply Threaded More More options
Print post
Permalink
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>

COMPARABLE.is_less

Reply Threaded More More options
Print post
Permalink
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