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

TypeVar(…, bound=RealLike) creates problems #10

Closed
posita opened this issue Jan 22, 2022 · 3 comments
Closed

TypeVar(…, bound=RealLike) creates problems #10

posita opened this issue Jan 22, 2022 · 3 comments

Comments

@posita
Copy link
Collaborator

posita commented Jan 22, 2022

First hinted at by @antonagestam in antonagestam/phantom-types#179 (comment), which led to an interesting work-around that numerary probably shouldn't impose, if it can avoid it.

@posita posita changed the title TypeVar(…, bound=RealLike) creates problems TypeVar(…, bound=RealLike) creates problems Jan 22, 2022
@posita
Copy link
Collaborator Author

posita commented Jan 30, 2022

@antonagestam, I still have no idea know why RealLike[IntegralLike[int]] works for your use case. 😕 That's a head-scratcher. 😊

I tried the following with some success, although I'm not sure if this does what you want:

diff --git a/src/phantom/interval.py b/src/phantom/interval.py
index e795106..55db040 100644
--- a/src/phantom/interval.py
+++ b/src/phantom/interval.py
@@ -26,7 +26,6 @@ from __future__ import annotations
 from typing import Any
 from typing import TypeVar
 
-from numerary.types import IntegralLike
 from numerary.types import RealLike
 from typing_extensions import Final
 from typing_extensions import Protocol
@@ -37,12 +36,12 @@ from .predicates import interval
 from .schema import Schema
 from .utils import resolve_class_attr
 
-N = TypeVar("N", bound=RealLike[IntegralLike[int]])
+N = TypeVar("N", covariant=True)
 Derived = TypeVar("Derived", bound="Interval")
 
 
 class IntervalCheck(Protocol):
-    def __call__(self, a: N, b: N) -> Predicate[N]:
+    def __call__(self, a: RealLike[N], b: RealLike[N]) -> Predicate[RealLike[N]]:
         ...
 
 
diff --git a/src/phantom/predicates/base.py b/src/phantom/predicates/base.py
index d13a76e..10c01b7 100644
--- a/src/phantom/predicates/base.py
+++ b/src/phantom/predicates/base.py
@@ -1,6 +1,10 @@
-from typing import Callable
 from typing import TypeVar
 
+from typing_extensions import Protocol
+
 T = TypeVar("T", bound=object, contravariant=True)
 
-Predicate = Callable[[T], bool]
+
+class Predicate(Protocol[T]):
+    def __call__(self, __: T, /) -> bool:
+        ...

I also tried this:

diff --git a/src/phantom/interval.py b/src/phantom/interval.py
index e795106..a62fd3d 100644
--- a/src/phantom/interval.py
+++ b/src/phantom/interval.py
@@ -26,7 +26,6 @@ from __future__ import annotations
 from typing import Any
 from typing import TypeVar
 
-from numerary.types import IntegralLike
 from numerary.types import RealLike
 from typing_extensions import Final
 from typing_extensions import Protocol
@@ -37,11 +36,11 @@ from .predicates import interval
 from .schema import Schema
 from .utils import resolve_class_attr
 
-N = TypeVar("N", bound=RealLike[IntegralLike[int]])
+N = TypeVar("N", bound=RealLike, contravariant=True)
 Derived = TypeVar("Derived", bound="Interval")
 
 
-class IntervalCheck(Protocol):
+class IntervalCheck(Protocol[N]):
     def __call__(self, a: N, b: N) -> Predicate[N]:
         ...
 

In both cases, I can eliminate the error you got, but I get a new one:

src/phantom/base.py:162: error: Too many arguments for "__init_subclass__" of "object"  [call-arg]
            super().__init_subclass__(**kwargs)
            ^

Again, I'm not sure I follow everything that code does, nor am I sure if any of the above is helpful. I'll continue to look into this, but if there's a way to reduce it, that might help?

@antonagestam
Copy link

Hmm, that change doesn't really make sense to me. The original signature def __call__(self, a: N, b: N) -> Predicate[N] reads like, given two values that are of the same subtype of RealLike, return a predicate that takes the same type as argument. So given a: float the signature requires b: float and Predicate[float]. Returning Predicate[int] would be a type error for that, and passing a: int, b: float is also a type error.

But the new signature def __call__(self, a: RealLike[N], b: RealLike[N]) -> Predicate[RealLike[N]], reads something like given two values that are of some subtypes of RealLike (not necessarily the same, this signature would accept a mix of float and ints), return a predicate that also takes some RealLike (again, not necessarily the same). The only thing that's enforced to be shared between a, b and the return value here is the operations that are typed to return N ...

So I don't think I want to apply that change to the type var usage.

Is changing Predicate from a Callable to a Protocol related?

In both cases, I can eliminate the error you got, but I get a new one:

That's a known mypy bug, I think it has been fixed recently: python/mypy#4660 Which mypy version are you running with?

@posita
Copy link
Collaborator Author

posita commented May 27, 2022

I finally have an update! I've since made some changes to numerary, which comes with some caveats…

The bad news: numerary has dropped support for Python 3.7. (Its support of 3.7 was illusory anyway, but now it no longer lies about it.) I know that phantom-types signals compatibility with 3.7, so this may be a deal killer? I'm not sure.

Other news: numerary's caching Protocol implementation now resides in beartype, which numerary now requires.

The good news: It appears this is no longer an issue, at least not with Mypy 0.960 (and possibly other recent versions). Here's my diff to antonagestam/phantom-types@0461b69 :

diff --git a/src/phantom/interval.py b/src/phantom/interval.py
index 0abe9ba..31dbe6e 100644
--- a/src/phantom/interval.py
+++ b/src/phantom/interval.py
@@ -26,7 +26,6 @@ from __future__ import annotations
 from typing import Any
 from typing import TypeVar

-from numerary.types import IntegralLike
 from numerary.types import RealLike
 from typing_extensions import Final
 from typing_extensions import Protocol
@@ -37,7 +36,7 @@ from .predicates import interval
 from .schema import Schema
 from .utils import resolve_class_attr

-N = TypeVar("N", bound=RealLike[IntegralLike[int]])
+N = TypeVar("N", bound=RealLike)
 Derived = TypeVar("Derived", bound="Interval")

Here's how it plays (from the phantom-types repo root):

$ pip list | grep numerary
numerary           0.4.0
$ mypy --version
mypy 0.960 (compiled: yes)
$ mypy src/phantom/interval.py
Success: no issues found in 1 source file
$ pre-commit run mypy --all-files
mypy.....................................................................Passed

I'm pretty sure this was the result of changes to Mypy rather than changes to numerary, but I don't know for sure. Either way, I think we can mark this as closed. @antonagestam, if that doesn't feel right, let me know and we can reopen and discuss further.

@posita posita closed this as completed May 27, 2022
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