Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

a way to refer to overloaded functions #623

Closed
bwo opened this issue Apr 10, 2019 · 5 comments
Closed

a way to refer to overloaded functions #623

bwo opened this issue Apr 10, 2019 · 5 comments

Comments

@bwo
Copy link

bwo commented Apr 10, 2019

Is there a way to refer to overloaded functions in argument position? It seems as if there isn't; reveal_type displays such functions as Overload(…), but there is no Overload exported from typing or typing_extensions.

My use case is generic functions:

from typing import overload, TypeVar, Generic, Callable, Union, cast

A = TypeVar('A')
B = TypeVar('B')
S = TypeVar('S')
T = TypeVar('T')


class Spam(Generic[A, B]):
    def __init__(self, a, b):
        # type: (A, B) -> None
        self.a = a
        self.b = b

    def map(self, f, g):
        # type: (Callable[[A], S], Callable[[B], T]) -> Spam[S, T]
        return Spam(f(self.a), g(self.b))

    def mapone(self, f):
        # type: ??? -> Spam[S, T]
        return self.map(
            cast(Callable[[A], S], f),  # ideally these casts would not be necessary either
            cast(Callable[[B], T], f)
        )

Given this:

@overload
def eggs(x):
    # type: (int) -> str
    pass


@overload
def eggs(x):
    # type: (str) -> bool
    pass


def eggs(x):
    return str(x) if isinstance(x, int) else False

I want to be able to do:

Spam(1, "2").mapone(eggs)

But this doesn't work. Although mypy recognizes that eggs is both a function str -> bool and a function int -> str, and the following typechecks:

Spam(1, "2").map(eggs, eggs)

, giving mapone the type Union[Callable[[A], S], Callable[[B], T]] -> Spam[S, T] requires me to give a type annotation to the second line here:

f = Spam("a", 1)
g = f.mapone(eggs)

and types g as Any. Any type annotation I give is accepted.

Giving mapone the alternative annotation Callable[[Union[A, B]], Union[S, T]] -> Spam[S, T] gives the error Argument 1 to "mapone" of "Spam" has incompatible type overloaded function; expected "Callable[[Union[str, int]], <nothing>]"

(Come to think of it, if it were possible to refer to overloaded functions in argument position like this, it would probably also be possible just to use Overload or whatever in the initial annotation of overloaded functions and avoid the repeated names, but that is a separate thing, I suppose.)

@JelleZijlstra
Copy link
Member

You can probably do this with a Protocol with an overloaded __call__ method.

@bwo
Copy link
Author

bwo commented Apr 10, 2019

Can you be more specific? Do you mean something like the following?

from typing import overload, TypeVar, Generic, Callable, Union, Protocol


A = TypeVar('A', covariant=True)
B = TypeVar('B', covariant=True)
S = TypeVar('S', contravariant=True)
T = TypeVar('T', contravariant=True)

class P(Protocol[S, T, A, B]):

    @overload
    def __call__(self, x):
        # type: (S) -> A
        pass


    @overload
    def __call__(self, x):
        # type: (T) -> B
        pass



class Spam(Generic[A, B]):
    def __init__(self, a, b):
        # type: (A, B) -> None
        self.a = a
        self.b = b


    def map(self, f, g):
        # type: (Callable[[A], S], Callable[[B], T]) -> Spam[S, T]
        return Spam(f(self.a), g(self.b))

    def mapone(self, f):
        # type: (P) -> Spam[S, T]
        return self.map(f, f)


@overload
def eggs(x):
    # type: (int) -> str
    pass


@overload
def eggs(x):
    # type: (str) -> bool
    pass


def eggs(x):
    return str(x) if isinstance(x, int) else False


f = Spam(1, "1")

g = f.mapone(eggs)
reveal_type(g)

because:

$ mypy foo.py
foo.py:58: error: Need type annotation for 'g'
foo.py:59: error: Revealed type is 'Any'
foo.py:59: error: Cannot determine type of 'g'

@bwo
Copy link
Author

bwo commented Apr 10, 2019

lol I didn't provide parameters to P. Let's try that again.

@bwo
Copy link
Author

bwo commented Apr 10, 2019

ye gods, it works, once I update P to be P[A, B, S, T].

@ilevkivskyi
Copy link
Member

Callback protocols is a recommended way of expressing this, and other tricky callbacks, see https://mypy.readthedocs.io/en/latest/protocols.html#callback-protocols

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants