In what case the contravariance of function arguments is useful?

35 Views Asked by At

We know that for two function types T1→T2 and S1→S2, T1→T2 ≤ S1→S2 iff S1≤T1 and T2≤S2 (ref). But I cannot come up with a realistic case where we want S1≤T1, as more likely we will use template/generic type to get around the Liskov substitution principle.

To facilitate the discussion, let's say we have the following classes (in Python)

class Animal: ...

class Cat(Animal): ...

class Habitat: ...

class Forest(Habitat): ...

on top of which we have "research center" classes, able to find the habitat for an animal. What won't work is

class AnimalResearchCenter:
    def find_habitat(self, a: Animal) -> Habitat:
        ...


class CatResearchCenter(AnimalResearchCenter):
    def find_habitat(self, c: Cat) -> Forest:
        ...

because the type of the argument of CatResearchCenter.find_habitat, Cat, should have been a supertype of Animal. In fact, mypy will complain

error: Argument 1 of "find_habitat" is incompatible with supertype "AnimalResearchCenter";
supertype defines the argument type as "Animal"  [override]
note: This violates the Liskov substitution principle

and we should use Generic for this case:

from typing import Generic, TypeVar

T = TypeVar("T", bound=Animal)

class AnimalResearchCenter(Generic[T]):
    def find_habitat(self, a: T) -> Habitat:
        ...


class CatResearchCenter(AnimalResearchCenter[Cat]):
    def find_habitat(self, c: Cat) -> Forest:
        ...

So the question is, in what case we will want a method like find_habitat(self, a: Animal) -> Habitat in the base class?

0

There are 0 best solutions below