Skip to content

Commit

Permalink
Integrate numerary to gain support for numeric tower
Browse files Browse the repository at this point in the history
  • Loading branch information
antonagestam committed Jan 22, 2022
1 parent 02cabe9 commit f7dead4
Show file tree
Hide file tree
Showing 5 changed files with 40 additions and 34 deletions.
1 change: 1 addition & 0 deletions .pre-commit-config.yaml
Expand Up @@ -61,6 +61,7 @@ repos:
- phonenumbers>=8.12.41
- pydantic
- types-setuptools
- numerary
- repo: https://github.com/mgedmin/check-manifest
rev: "0.47"
hooks:
Expand Down
19 changes: 9 additions & 10 deletions src/phantom/interval.py
Expand Up @@ -25,8 +25,9 @@ def take_portion(portion: Portion, whole: Natural) -> float:

from typing import Any
from typing import TypeVar
from typing import Union

from numerary.types import IntegralLike
from numerary.types import RealLike
from typing_extensions import Final
from typing_extensions import Protocol

Expand All @@ -36,7 +37,7 @@ def take_portion(portion: Portion, whole: Natural) -> float:
from .schema import Schema
from .utils import resolve_class_attr

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


Expand All @@ -49,9 +50,7 @@ def __call__(self, a: N, b: N) -> Predicate[N]:
neg_inf: Final = float("-inf")


# See issue as to why the numeric tower isn't used for kind here.
# https://github.com/python/mypy/issues/3186
class Interval(Phantom[float], bound=Union[int, float], abstract=True):
class Interval(Phantom[float], bound=RealLike, abstract=True):
"""
Base class for all interval types, providing the following class arguments:
Expand All @@ -63,14 +62,14 @@ class Interval(Phantom[float], bound=Union[int, float], abstract=True):
"""

__check__: IntervalCheck
__low__: float
__high__: float
__low__: RealLike
__high__: RealLike

def __init_subclass__(
cls,
check: IntervalCheck | None = None,
low: float = neg_inf,
high: float = inf,
low: RealLike = neg_inf,
high: RealLike = inf,
**kwargs: Any,
) -> None:
resolve_class_attr(cls, "__low__", low)
Expand All @@ -90,7 +89,7 @@ def parse(cls: type[Derived], instance: object) -> Derived:
)


def _format_limit(value: float) -> str:
def _format_limit(value: RealLike) -> str:
if value == inf:
return "∞"
if value == neg_inf:
Expand Down
18 changes: 10 additions & 8 deletions src/phantom/predicates/interval.py
Expand Up @@ -3,53 +3,55 @@
strictly between the upper and lower bounds. There are corresponding phantom types that
use these predicates in :py:mod:`phantom.interval`.
"""
from numerary.types import RealLike

from .base import Predicate
from .utils import bind_name


def open(low: float, high: float) -> Predicate[float]:
def open(low: RealLike, high: RealLike) -> Predicate[RealLike]:
"""
Create a predicate that succeeds when its argument is in the range ``(low, high)``.
"""

@bind_name(open, low, high)
def check(value: float) -> bool:
def check(value: RealLike) -> bool:
return low <= value <= high

return check


def open_closed(low: float, high: float) -> Predicate[float]:
def open_closed(low: RealLike, high: RealLike) -> Predicate[RealLike]:
"""
Create a predicate that succeeds when its argument is in the range ``(low, high]``.
"""

@bind_name(open_closed, low, high)
def check(value: float) -> bool:
def check(value: RealLike) -> bool:
return low <= value < high

return check


def closed_open(low: float, high: float) -> Predicate[float]:
def closed_open(low: RealLike, high: RealLike) -> Predicate[RealLike]:
"""
Create a predicate that succeeds when its argument is in the range ``[low, high)``.
"""

@bind_name(closed_open, low, high)
def check(value: float) -> bool:
def check(value: RealLike) -> bool:
return low < value <= high

return check


def closed(low: float, high: float) -> Predicate[float]:
def closed(low: RealLike, high: RealLike) -> Predicate[RealLike]:
"""
Create a predicate that succeeds when its argument is in the range ``[low, high]``.
"""

@bind_name(closed, low, high)
def check(value: float) -> bool:
def check(value: RealLike) -> bool:
return low < value < high

return check
30 changes: 16 additions & 14 deletions src/phantom/predicates/numeric.py
@@ -1,88 +1,90 @@
from numerary.types import RealLike

from .base import Predicate
from .boolean import negate
from .generic import equal
from .utils import bind_name


def less(n: float) -> Predicate[float]:
def less(n: RealLike) -> Predicate[RealLike]:
"""
Create a new predicate that succeeds when its argument is strictly less than ``n``.
"""

@bind_name(less, n)
def check(value: float) -> bool:
def check(value: RealLike) -> bool:
return value < n

return check


def le(n: float) -> Predicate[float]:
def le(n: RealLike) -> Predicate[RealLike]:
"""
Create a new predicate that succeeds when its argument is less than or equal to
``n``.
"""

@bind_name(le, n)
def check(value: float) -> bool:
def check(value: RealLike) -> bool:
return value <= n

return check


def greater(n: float) -> Predicate[float]:
def greater(n: RealLike) -> Predicate[RealLike]:
"""
Create a new predicate that succeeds when its argument is strictly greater than
``n``.
"""

@bind_name(greater, n)
def check(value: float) -> bool:
def check(value: RealLike) -> bool:
return value > n

return check


def ge(n: float) -> Predicate[float]:
def ge(n: RealLike) -> Predicate[RealLike]:
"""
Create a new predicate that succeeds when its argument is greater than or equal to
``n``.
"""

@bind_name(ge, n)
def check(value: float) -> bool:
def check(value: RealLike) -> bool:
return value >= n

return check


def positive(n: float) -> bool:
def positive(n: RealLike) -> bool:
"""Return :py:const:`True` when ``n`` is strictly greater than zero."""
return greater(0)(n)


def non_positive(n: float) -> bool:
def non_positive(n: RealLike) -> bool:
"""Return :py:const:`True` when ``n`` is less than or equal to zero."""
return le(0)(n)


def negative(n: float) -> bool:
def negative(n: RealLike) -> bool:
"""Return :py:const:`True` when ``n`` is strictly less than zero."""
return less(0)(n)


def non_negative(n: float) -> bool:
def non_negative(n: RealLike) -> bool:
"""Return :py:const:`True` when ``n`` is greater than or equal to zero."""
return ge(0)(n)


def modulo(n: float, p: Predicate[float]) -> Predicate[float]:
def modulo(n: RealLike, p: Predicate[RealLike]) -> Predicate[RealLike]:
"""
Create a new predicate that succeeds when its argument modulo ``n`` satisfies the
given predicate ``p``.
"""

@bind_name(modulo, n, p)
def check(value: float) -> bool:
def check(value: RealLike) -> bool:
return p(value % n)

return check
Expand Down
6 changes: 4 additions & 2 deletions src/phantom/sized.py
Expand Up @@ -24,6 +24,8 @@ class SpecificSize(PhantomSized[int], len=interval.open(5, 10)):
from typing import Sized
from typing import TypeVar

from numerary.types import RealLike

# We attempt to import _ProtocolMeta from typing_extensions to support Python 3.7 but
# fall back the typing module to support Python 3.8+. This is the closest I could find
# to documentation of _ProtocolMeta.
Expand Down Expand Up @@ -77,9 +79,9 @@ class PhantomSized(
bound=SizedIterable,
abstract=True,
):
"""Takes class argument ``len: Predicate[float]``."""
"""Takes class argument ``len: Predicate[RealLike]``."""

def __init_subclass__(cls, len: Predicate[float], **kwargs: Any) -> None:
def __init_subclass__(cls, len: Predicate[RealLike], **kwargs: Any) -> None:
super().__init_subclass__(
predicate=boolean.both(
boolean.negate(generic.of_type(mutable)),
Expand Down

0 comments on commit f7dead4

Please sign in to comment.