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?
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.