[comp.lang.eiffel] Covariance vs Contravariance.

paj@uk.co.gec-mrc (Paul Johnson) (03/11/91)

Another thought to add to the co/contra variance debate.  Why not have
both?

Page 80 of `Eiffel: The Language' (version 2.2) states that:

A signature S = ( <X1, X2, X3, ... >, <R> ) conforms to S' = ( <X1',
X2', X3', ... ), <R'> ) if and only if:

1: The two sequences have the same number of elements.

2: Every type in each of the two sequence components of S conforms to
   the corresponding type in the corresponding component of S'.


Note for the uninitiated:

---------------------------------------------------------------------

The term `signature' refers to the types taken and returned by a
procedure or function.  The Xn terms are the argument types and the R
terms are the return types (if any).  Part 2 (in conjunction with one
or two other rules) states that a feature in a descendant class can
only take and return types which are descendants of the types taken
and returned by the same function in the ancestor where it was
defined.

This is known as covariance, and is needed in Eiffel for the
definition of functions which take arguments of type `like f' where
`f' is some feature which may be redefined in subsequent types.  It
also leads to type errors when polymorphism is applied to the class
which contains the function.  Suppose we have a class ANCESTOR which
defines a function `foo( a: ANCESTOR )'.  We then derive a class
DESCENDANT which inherits from ANCESTOR and redefines `foo' to be
`foo( a: DESCENDANT )'.  We then create an instance of DESCENDANT and
set a reference of type ANCESTOR to this new instance.  It is now
possible to call the new version of `foo' (which expects something
of type `DESCENDANT' with an argument of type `ANCESTOR').  The
contrary scheme (contravariance) would have signatures conforming if
the types in the X sequence of S' conformed to the types in the X
sequence of S.  This is the other way around and causes great
problems.

Interestingly, the Software Contracting Principle applied to
assertions and requirements in descendant classes insists on a
contravariant scheme, but that is not what we are discussing here.

---------------------------------------------------------------------

End of note.  Start of suggestion.

Why not remove the restrictions on the argument types.  Contravariant
typing may not be very useful, but there is no reason to disallow it
since it is always type safe.  If the type checker can catch misuses
of covariant typing then the compiler can allow both.

Paul.

PS for Dr. Meyer.  This is not the Sibling-Supertype rule I talked
about at TOOLS '91.  Two papers describing that are on their way to
you.

Paul Johnson                               UUCP: <world>!mcvax!ukc!gec-mrc!paj
--------------------------------!-------------------------|-------------------
GEC-Marconi Research is not 	| Telex: 995016 GECRES G  | Tel: +44 245 73331
responsible for my opinions.	| Inet: paj@gec-mrc.co.uk | Fax: +44 245 75244

rick@tetrauk.UUCP (Rick Jones) (03/13/91)

In article <860@puck.mrcu> paj@uk.co.gec-mrc (Paul Johnson) writes:
>
>Another thought to add to the co/contra variance debate.  Why not have
>both?
>
> [detailed definition of the co/contra variant principles]
>
>Why not remove the restrictions on the argument types.  Contravariant
>        ^^^^^^^^^^^^^^^^^^^^^^^
>typing may not be very useful, but there is no reason to disallow it
>since it is always type safe.  If the type checker can catch misuses
>of covariant typing then the compiler can allow both.

I'm not sure if you meant to say that, but you can't actually _remove_ the
restrictions.  What you can do is allow arguments to be redefined as either
ancestors or descendants of the inherited definitions.  I.e given a portion of
an inheritance tree:

		A
	       / \
	      /   \
	     B     D
	    /
	   /
	  C

then a function "foo (arg: B)" in some class may be redefined in a descendant
as either "foo (arg: C)" (covariance), or "foo (arg: A)" (contravariance).  It
_cannot_ be redefined as "foo (arg: D)".

I would agree in principle that since contravariance is guaranteed safe, there
seems no reason to disallow it because covariance is supported.  However, this
leads to a logical anomaly.  To take an extreme case, consider the following:

	class	X	declares: foo (arg: STRING)
		|
		|
	class	Y	redefines: foo (arg: ANY)  -- legal by contravariance
		|
		|
	class	Z	redefines: foo (arg: TREE)  -- legal by covariance

If class Y does nothing else apart from redefining foo, then you could arguably
say that class Z should be able to inherit directly from class X.  This _would_
require removal of all type checks on arguments, which seems from a practical
point of view to be a nonsense.

It does suggest that allowing co- and contra- variance to coexist would lead in
practice to more confusion than it resolved.

-- 
Rick Jones, Tetra Ltd.  Maidenhead, Berks, UK
rick@tetrauk.uucp

The dynamics of a freeway approximates to a shoal of fish in 1.5 dimensions