pythongenericsinheritancemypyinvariance

mypy generic subclass leads to incompatible types


I'm toying around with implementing monoids with type hinting. To that end I've written:

M = TypeVar('M')

class Monoid(Generic[M]):
    ...
    def append(self, m: 'Monoid[M]') -> 'Monoid[M]':
        raise NotImplementedError()

When using this in a subclass, e.g

A = TypeVar('A')

class List(Monoid[A], Generic[A]):
    def __init__(self, *values: A) -> None:
        self._values = tuple(values)
    ...
    def append(self, m: 'List[A]') -> 'List[A]':
        return List(*(self.values + m.values))

I get error: Argument 1 of "append" incompatible with supertype "Monoid". Since List is a proper subclass of Monoid, I would expect this to be able to type. What am I doing wrong?


Solution

  • Well, your List class isn't a proper subtype of Monoid. After all, you stated that all Monoids must have an append method that can accept any arbitrary Monoid or subclass of Monoid -- so, why is it ok to narrow List so that its append can accept only specifically List?

    It's a violation of the Liskov substitution principle.

    You can work around this specific case by using a generic self:

    M = TypeVar('M')
    T = TypeVar('T')
    
    class Monoid(Generic[M]):
        ...
        def append(self: T, m: T) -> T:
            raise NotImplementedError()
    

    Now, you're expressing that all subclasses of Monoid must implement an append method that accepts specifically whatever that subclass type is. With this new version of Monoid, your List class is now typesafe.