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

Types as function namespaces #5980

Closed
jesaerys opened this issue Nov 30, 2018 · 2 comments
Closed

Types as function namespaces #5980

jesaerys opened this issue Nov 30, 2018 · 2 comments

Comments

@jesaerys
Copy link

I often wish that types could also be namespaces for functions. To motivate this idea, let me start with a simple example. Say I need some functions that apply to arbitrary 2-tuples. The simplest solution is to write a module defining a Pair type along with the functions I need:

# pair.py
from typing import Tuple, TypeVar

A = TypeVar('A')
B = TypeVar('B')

Pair = Tuple[A, B]

def fst(pair: Pair[A, B]) -> A:
    a, _ = pair
    return a

def snd(pair: Pair[A, B]) -> B:
    _, b = pair
    return b

I would use my module like this:

# test.py
import pair
from pair import Pair

def f(p: Pair[int, float]) -> float:
    return pair.fst(p) + pair.snd(p)

p: Pair[int, float] = (1, 1.5)
assert f(p) == 2.5

This works and it typechecks with mypy. However, naming is a bit cumbersome with this approach. I can't use the pair identifier for Pair variables because I've already used it for my module, and I'm forced to resort to something like p instead. Also, it would be nicer if the functions fst and snd had a clearer association with the type Pair.

I'd much rather write something like this for my test:

# test.py
from pair import Pair

def f(pair: Pair[int, float]) -> float:
    return Pair.fst(pair) + Pair.snd(pair)

pair: Pair[int, float] = (1, 1.5)
assert f(pair) == 2.5

where Pair is now both a type and a namespace for associated functions.

Is it possible for mypy to support this?

I'm not making any syntax proposals here, but I'll note that the following does work at runtime:

from typing import Tuple, TypeVar

A = TypeVar('A')
B = TypeVar('B')

class Pair(Tuple[A, B]):

    def fst(pair: Pair[A, B]) -> A:
        a, _ = pair
        return a

    def snd(pair: Pair[A, B]) -> B:
        _, b = pair
        return b


def f(pair: Pair[int, float]) -> float:
    return Pair.fst(pair) + Pair.snd(pair)

pair: Pair[int, float] = (1, 1.5)
assert f(pair) == 2.5

Unfortunately, it does not typecheck with mypy, first of all because generic tuples are not allowed (#685). Second, even if generic tuples were supported, my assignment statement for pair would fail with an "Incompatible type" error because the expression has type Tuple[int, float] while the variable has type Pair[int, float], and mypy sees Pair as a subtype of Tuple due to the class definition.

I'm really curious to hear people's thoughts on this.

Thanks.

@hauntsaninja
Copy link
Collaborator

Use staticmethod to use a class as a namespace.

@jesaerys
Copy link
Author

I completely lost track of this. Apparently I was looking for a way to associate methods/functions with a type without creating a subclass, but it's been a long time since I opened this issue and I can't remember why. As far as I can tell, this still isn't really possible and my first example (type alias + independent functions) is the most straightforward workaround.

That aside, I'm happy to see that subclassing generic tuples now typechecks on master, I assume as of #13396. Full example:

from __future__ import annotations

from typing import Tuple, TypeVar

A = TypeVar('A')
B = TypeVar('B')

class Pair(Tuple[A, B]):

    @staticmethod
    def fst(pair: Pair[A, B]) -> A:
        a, _ = pair
        return a

    @staticmethod
    def snd(pair: Pair[A, B]) -> B:
        _, b = pair
        return b


def f(pair: Pair[int, float]) -> float:
    return Pair.fst(pair) + Pair.snd(pair)

pair: Pair[int, float] = Pair((1, 1.5))
assert f(pair) == 2.5

(Thanks to @hauntsaninja for reminding me that fst and snd should be decorated with @staticmethod.)

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

2 participants