python / mypy

Optional static typing for Python
https://www.mypy-lang.org/
Other
18.19k stars 2.78k forks source link

Polymorphism question #5040

Open kris-brown opened 6 years ago

kris-brown commented 6 years ago

Hi, I'm very new to mypy. I have a question for how to handle the following kinds of polymorphic functions:

import typing as typ

T = typ.TypeVar('T')

def identity(x:T)->T:
    return x 

A = typ.TypeVar('A')
B = typ.TypeVar('B')
C = typ.TypeVar('C')

def compose_and_apply(x : A
                     ,f : typ.Callable[[A],B]  = identity
                     ,g : typ.Callable[[B],C]  = identity
                     ) -> C:
    return g(f(x))

I get the error: Incompatible default for argument "f" (default has type "Callable[[T],T]", argument has type "Callable[[A],B]")

I'd like MyPy to see that a function (T->T) can be a valid instance of type (A->B). Is there a way to declare the type variables T,A,B, and C in a way that says "they might be the same, but they might be different"?

ilevkivskyi commented 6 years ago

I am not sure it is possible to express this now. Maybe you can just write cast(Callable[[A], B], identity)?

msullivan commented 6 years ago

This is a bug, in my opinion, but not a very high priority one. Dropping the default args and just calling it with identity twice produces an error.

maxfischer2781 commented 3 years ago

Ping since this is proving to be very annoying for my library.

The minimal repro is this code:

from typing import TypeVar, Callable

T = TypeVar("T")
R = TypeVar("R")

async def call_def(x: T, func: Callable[[T], R] = lambda x: x) -> R:
    return func(x)
main.py:6: error: Incompatible default for argument "func" (default has type "Callable[[T], T]", argument has type "Callable[[T], R]")
main.py:6: error: Incompatible return value type (got "T", expected "R")
Found 2 errors in 1 file (checked 1 source file)

This is somewhat okay when dealing with plain callables, since it is obvious were MyPy trips.

Yet in my case of the default being an async def awaitable and the argument an optional-sync-or-async call we get this

error: Incompatible default for argument "key" (default has type "Callable[[T], Coroutine[Any, Any, T]]", argument has type "Union[Callable[[T], R], Callable[[T], Awaitable[R]], None]")

and it takes quite a while to find out this a false positive.


Interestingly enough, MyPy has no problem when the function is passed in:

async def call_arg(x: T, func: Callable[[T], R]) -> R:
    return func(x)

call_arg(1, lambda x:x)

However, this seems to work only skin-deep. Using the original code without defaults and doing compose_and_apply(1, identity, identity) results in the error:

error: Argument 2 to "compose_and_apply" has incompatible type "Callable[[T], T]"; expected "Callable[[int], T]"