[comp.lang.eiffel] Errors in COMPARABLE and PART_COMPAR

kim@helios.enea.se (Kim Wald`n) (05/05/90)

Some days ago I used an example with sorted lists of airplanes
in an Eiffel course to show how easy it is to introduce meaningful
order relations for high level objects.

Much to my surprise, explaining how this is elegantly achieved
through the standard library immediately revealed several serious errors
in the kernel classes COMPARABLE and PART_COMPAR.

1)  The postconditions in COMPARABLE are circularly defined, so when 
    assertion checking is on, the result is an infinite loop.

    The postconditions of "<" and ">=" are each defined in terms of the
    other operation, and the same holds for ">" and "<=".

    For the first two operators, this might go unnoticed if the postcondition
    of the deferred routine "<" is not repeated in descendant classes,
    but any use of the last two with full assertion checking bites directly.

    Instead, the postcondition for "<" should be dropped in COMPARABLE,
    and the postconditions for "<=", ">", and ">=" should all be
    expressed in terms of "<".

2)  The next error is more serious: the body of "<=" is

		Result := (Current < other) or (equal (other))

    in both COMPARABLE and PART_COMPAR.

    Now "equal" is a feature inherited from class ANY, which makes a
    field-by-field comparison of whole objects to find out whether they
    are identical in some sense (shallow semantics in this case).

    However, this is not the same as "ordering" equality.

    When a transport company ranks its trucks by freight capacity,
    it doesn't seem reasonable to have the respective hood colors influence 
    the outcome of "<=" operations.  But what is even worse, is that it
    will often lead to situations where none of "<", "<=", ">", ">=" holds
    for a pair of objects, thus violating the very core of a total order
    relation in the case of class COMPARABLE.  This may lead to any kind
    of strange behavior in applications.

    In PART_COMPAR, which implements a partial order relation, there is 
    no reasonable way to express "ordering" equality only in terms of "<",
    so either "<=" or ">=" must also be deferred (and the other expressed
    in terms of the deferred one).

    For the same reasons, the obsolete routines "eq" and "ne" should be
    removed, since they would have to be changed to deferred anyway.

    In COMPARABLE, however, it suffices to replace the body of "<=" by

		Result := not (other < Current)

    Below are the complete context diffs (diff -c) to fix the problems.

    If you have access to Larry Walls ``patch'' program, you may simply
    goto directory .../Eiffel/library/kernel, and feed this entire article 
    verbatim on standard input to ``patch'', which will then automatically
    update the two classes and leave the originals as ``comparable.e.orig''
    and ``part_compar.e.orig'' respectively.

*** comparable.e	Fri May  4 18:40:28 1990
--- comparable.e.new	Fri May  4 17:33:16 1990
***************
*** 22,37 ****
  	infix "<" (other: like Current): BOOLEAN is
  			-- Is current element less than `other'?
  		deferred
- 		ensure
- 			Result implies (not (Current >= other))
  		end; -- "<"
  
  	infix "<=" (other: like Current): BOOLEAN is
  			-- Is current element less than or equal to `other'?
  		do
! 			Result := (Current < other) or (equal (other))
  		ensure
! 			Result implies (not (Current > other))
  		end; -- "<="
  
  	infix ">" (other: like Current): BOOLEAN is
--- 22,35 ----
  	infix "<" (other: like Current): BOOLEAN is
  			-- Is current element less than `other'?
  		deferred
  		end; -- "<"
  
  	infix "<=" (other: like Current): BOOLEAN is
  			-- Is current element less than or equal to `other'?
  		do
! 			Result := not (other < Current)
  		ensure
! 			Result implies (not (other < Current))
  		end; -- "<="
  
  	infix ">" (other: like Current): BOOLEAN is
***************
*** 39,45 ****
  		do
  			Result := other < Current
  		ensure
! 			Result implies (not (Current <= other))
  		end; -- ">"
  
  	infix ">=" (other: like Current): BOOLEAN is
--- 37,43 ----
  		do
  			Result := other < Current
  		ensure
! 			Result implies (other < Current)
  		end; -- ">"
  
  	infix ">=" (other: like Current): BOOLEAN is

*** part_compar.e	Fri May  4 21:23:25 1990
--- part_compar.e.new	Fri May  4 17:36:30 1990
***************
*** 22,29 ****
  
  	infix "<=" (other: like Current): BOOLEAN is
  			-- Is current element less than or equal to `other'?
! 		do
! 			Result := (Current < other) or (equal (other))
  		end; -- "<="
  
  	infix ">" (other: like Current): BOOLEAN is
--- 22,28 ----
  
  	infix "<=" (other: like Current): BOOLEAN is
  			-- Is current element less than or equal to `other'?
! 		deferred
  		end; -- "<="
  
  	infix ">" (other: like Current): BOOLEAN is
***************
*** 35,41 ****
  	infix ">=" (other: like Current): BOOLEAN is
  			-- Is current element greater than or equal to `other'?
  		do
! 			Result := (other < Current) or (equal (other))
  		end; -- ">="
  
  -- obsolete routines
--- 34,40 ----
  	infix ">=" (other: like Current): BOOLEAN is
  			-- Is current element greater than or equal to `other'?
  		do
! 			Result := other <= Current
  		end; -- ">="
  
  -- obsolete routines
***************
*** 45,62 ****
  		do
  			Result := Current < other
  		end; -- lt
- 
- 	eq (other: like Current): BOOLEAN
- 						obsolete "Use ``equal''" is
- 		do
- 			Result := equal (other)
- 		end; -- eq
- 
- 	ne (other: like Current): BOOLEAN
- 						obsolete "Use negation of ``equal''" is
- 		do
- 			Result := not equal (other)
- 		end; -- ne
  
  	le (other: like Current): BOOLEAN
  						obsolete "Use infix ``<=''" is
--- 44,49 ----

-- Kim Walden
Enea Data, Sweden
kim@enea.se

stephan@eiffel.UUCP (Philippe Stephan) (05/08/90)

In response to <KIM.90May5032047@helios.enea.se>, about "Errors in
COMPARABLE and PART_COMPAR", 2 things have to be said:

	1) The forthcoming version of the compiler allows circularly
defined assertions: when assertion checking involves a call to a
routine, no assertion check is made on the called routine. What's
pointed out is actually a bug in 2.2.

	2) The function `equal' from ANY is redefinable. Its default
behavior is to do a field by field comparison, but why not?
The freight capacity of a truck can of course be the only criterion
for a sort, if you want so.

Philippe Stephan.
stephan@eiffel.com

kim@helios.enea.se (Kim Wald`n) (05/09/90)

In article <325@eiffel.UUCP> stephan@eiffel.UUCP (Philippe Stephan) writes:

>	   1) The forthcoming version of the compiler allows circularly
>   defined assertions: when assertion checking involves a call to a
>   routine, no assertion check is made on the called routine. What's
>   pointed out is actually a bug in 2.2.

Seems reasonable.

>	   2) The function `equal' from ANY is redefinable. Its default
>   behavior is to do a field by field comparison, but why not?
>   The freight capacity of a truck can of course be the only criterion
>   for a sort, if you want so.

You are missing the point.

First, class COMPARABLE implements a total order relation.
In a total order relation, strict order and equality are not independent.

	a > b   is identical to   b < a 

and

	a = b   is identical to   ( not a < b  and  not a > b )

BY  DEFINITION.

Any other implementation of "=" breaks the deepest meaning of the class,
as fundamental properties, like

	"For all pairs, exactly one of 
		a < b
		a > b
		a = b
	is true"

will no longer hold.  This is the worst that can happen to a class.

So the current implementation of COMPARABLE is plain wrong, and can
easily cause subtle bugs in applications using it.

It can only be correct by accident, in cases when ``equal'',
whether redefined or not, HAPPENS to coincide with the definition above.

Worse still, only "<=" in COMPARABLE uses ``equal'', while ">=" has the
correct implementation
	Result := not (Current < other)
so the meanings of the "=" parts in "<=" and ">=" are not even the same!

(I'm using "=" as a simple notation for the corresponding concept in
order relations here.  It is not to be confused with the non-redefineable
reserved Eiffel operator "=".)

The correct thing is to implement "<=" as
	Result := not (other < Current)
in accordance with the definition of total order relations (as in ">=").

There is no reason whatsoever to involve ``equal'' from ANY, which is 
a feature to check object identity.  The equality concept wanted as part
of an order relation is something else.

When you are ordering trucks by freight capacity, it seems reasonable to
consider a yellow 2 ton truck as neither greater nor smaller than a
green 2 ton truck. If you get a freight order for 2 tons, any one will do.

But you still want a way to distinguish between them, should you ever need 
to smuggle 2 tons of whisky through the green forrest at the frontier,
where a yellow truck is much too conspicuous.

Second, class PART_COMPART implements a partial order relation, which means
that for some pairs neither of 
	a < b
	a > b
	a = b
may hold.

Therefore, the meaning of "=" cannot be inferred from "<", but their semantics 
are still closely linked, so a descendant class should define both for
consistency.  This means that one of "<=" and ">=" should also be deferred.

As should be clear from the argumentation above, using ``equal'' from ANY 
in a default implementation of "<=", even if it is redefineable, is 
plain wrong for class COMPARABLE, and ad hoc and confusing at best 
for class PART_COMPAR.

This may be corrected by applying the diffs from my previous article.


-- Kim Walden
Enea Data, Sweden
kim@enea.se