5.5. Type Conformance

In order for a child class to legally subtype from a parent abstract class, we have to determine whether the signatures in the child class are consistent with the signatures in the parent class. The consistency check must ensure that in any code, if the parent class is replaced by the child class, the code would continue to work. This guarantee of substuitability which is guaranteed to be safe at compile time is at the heart of the Sather guarantee of type-safety.

5.5.1. Contravariant conformance

The type-safe rule for determining whether a signature in a child class is consistent with the definition of the signature in the parent class is referred to as the conformance rule[1]. The rule is quite simple, but counter-intuitive at first. Assume the simple abstract classes which we will use for argument types
abstract class $UPPER is ...
abstract class $MIDDLE < $UPPER is...
abstract class $LOWER < $MMIDDLE is ...

[1] Frequently called the contravariant conformance rule to distinguish it from the more restrictive C++ rule of invariance and the unsafe Eiffel rule (of covariance in the argument types). Hence, the co- vs. contra variance debate just refers to the behavior of the argument types.

If we now have an abstract class with a signature
abstract class $SUPER is
   foo(a1:$MIDDLE, out a2:$MIDDLE, inout a3:$MIDDLE):$MIDDLE;
end;

What are the arguments types of foo in a subytpe of $SUPER? The rule says that in the subtype definition of foo

Thus, a valid subtype of $SUPER is
abstract class $SUPER is
   foo(a1:$MIDDLE, out a2:$MIDDLE, inout a3:$MIDDLE):$MIDDLE;
end;

We will explain this rule and its ramifications using an extended example.

Suppose we start with herbivores and carnivores, each of which are capable of eating
abstract class $HERBIVORE is
   eat(food:$PLANT);
...

abstract class $CARNIVORE is
   eat(food:$MEAT);
...

abstract class $FOOD is ...
abstract class $PLANT < $FOOD is...
abstract class $MEAT < $FOOD is...

What does not work

It would appear that both herbivores and carnivores could be subtypes of omnivores.
abstract class $OMNIVORE is eat(food:$FOOD);
abstract class $CARNIVORE < $OMNIVORE is ...
abstract class $HERBIVORE < $OMNIVORE is ...

However, subtyping conformance will not permit this! The argument to eat in $HERBIVORE is $PLANT which is not the same as or a supertype of $FOOD, the argument to eat in $OMNIVORE.

To illustrate this, consider a variable of type $OMNIVORE, which holds a herbivore.
cow:$HERBIVORE := #COW; -- assigned to a COW object
animal:$OMNIVORE := cow;
meat:$MEAT;
animal.eat(meat);

This last call would try to feed the animal meat, which is quite legal according to the signature of $OMNIVORE::eat($FOOD), since $MEAT is a subtype of $FOOD. However, the animal happens to be a cow, which is a herbivore and cannot eat meat.

What does work

When contravariance does not permit a subtyping relationship this is usually an indication of an exceptional case or an error in our conceptual understanding. In this case, we note that omnivores are creatures that can eat anything. But a herbivore really is not an omnivore, since it cannot eat anything. More importantly, a herbivore could not be substuted for an omnivore. It is, however, true that an omnivore can act as both a carnivore and a herbivore.
abstract class $CARNIVORE is
   eat(food:$MEAT);
...

abstract class $HERBIVORE is
   eat(food:$PLANT);
...

abstract class $OMNIVORE < $HERBIVORE, $CARNIVORE is
   eat(food:$FOOD);
...

The argument of eat in the omnivore is $FOOD, which is a supertype of $MEAT, the argument of eat in $CARNIVORE. $FOOD is also a supertype of $PLANT which is the argument of eat in $HERBIVORE.

5.5.2. Subtyping = substitutability

A key distinction is that between is-a and as-a relationships. When a class, say $OMNIVORE subtypes from another class such as $CARNIVORE, it means that an omnivore can be used in any code which deals with carnivores i.e. an omnivore can substitute for a carnivore. In order for this to work properly, the child class omnivore must be able to behave as-a carnivore. In many cases, an is-a relationship does not satisfy the constraints required by the as-a relationship. The contravariant conformance rule captures the necessary as-a relationship between a subtype and a supertype.