Skip to content

generic methods/generic self don't seem to play with parameterized generics #4395

Closed
@bwo

Description

@bwo
Contributor

(There are a couple of other issues that I think might be the same thing as this question, but I'm not sure, because they're posed at a lower level—apologies if this is actually a duplicate. #3153, maybe?)

The generic methods and generic self docs show how write a class with methods that accept/return arguments of the most precise type, so things like this work as intended:

from typing import Generic, TypeVar

T = TypeVar('T', bound='Modular')


### this works! yay!
class Modular(object):
    modulus = 0  # type: int

    def __init__(self, i):
        # type: (int) -> None
        self.i = i % self.modulus

    def __add__(self, other):
        # type: (T, T) -> T
        return self.__class__((self.i + other.i) % self.modulus)


class Mod5(Modular):
    modulus = 5


class Mod6(Modular):
    modulus = 6


Mod5(3) + Mod6(3)  # statically "disallowed"

mypy won't let me add Mod5s to Mod6s, hooray. But what if the relevant class is itself generic? This doesn't work:

In = TypeVar('In', contravariant=True)
Out = TypeVar('Out', covariant=True)
A = TypeVar('A')
B = TypeVar('B')

F = TypeVar('F', bound='FunctionLike') 

class FunctionLike(Generic[In, Out]):
    def call(self, arg):
        # type: (In) -> Out
        raise NotImplementedError

    def compose(self, other):
        # type: (F[In, Out], F[Out, B]) -> F[In, B]  # can't index `F`, so this doesn't work
        raise NotImplementedError

The type of compose shouldn't be (FunctionLike[In, Out], FunctionLike[Out, B]) -> FunctionLike[In, B], because then any two instances of any two subclasses could be composed together. It can't be (F, F) -> F because then all the type parameters are the same.

Is there a way to express this?

Activity

elazarg

elazarg commented on Dec 19, 2017

@elazarg
Contributor

Related: #2354

ilevkivskyi

ilevkivskyi commented on Dec 19, 2017

@ilevkivskyi
Member

Many aspects of #2354 and #3153 can be fixed (and I actually wanted to do this soon).

But, your particular feature request is too tricky. This is what is known in other languages as higher kinds. A normal generic is something that expects a type (or several types) and "returns" a type. While in your case, FunctionLike.compose is something that itself expects a type constructor (F in your case) that takes and "returns" a type. If you are familiar wit Haskell, then normal generics (supported by mypy) are * -> *, * -> * -> * (two type variables), etc. You want something like (* -> *) -> * -> * (if I fix In and Out for simplicity). Supporting this is more tricky (especially the type inference), so don't expect this in the near future. Fortunately, in Python it is OK to write Any (or other type less precise than you would want).

msullivan

msullivan commented on Jan 28, 2020

@msullivan
Collaborator

Closing this in favor of the other issues linked

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @bwo@msullivan@elazarg@ilevkivskyi

        Issue actions

          generic methods/generic self don't seem to play with parameterized generics · Issue #4395 · python/mypy