From 6ef0537247e2e51acd856ba91bc4096d7df1bd8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Gutowski?= Date: Tue, 16 Nov 2021 20:54:25 +0100 Subject: [PATCH 01/10] Filter for maybe --- returns/maybe.py | 24 ++++++++++++++++++++++++ tests/test_maybe/test_maybe_filter.py | 10 ++++++++++ 2 files changed, 34 insertions(+) create mode 100644 tests/test_maybe/test_maybe_filter.py diff --git a/returns/maybe.py b/returns/maybe.py index 7a443059a..9174f9a82 100644 --- a/returns/maybe.py +++ b/returns/maybe.py @@ -141,6 +141,20 @@ def bind_optional( """ + def filter(self, + function: Callable[[_ValueType], bool], + ) -> 'Maybe[_ValueType]': + """ + Apply a predicate over the value. If the predicate returns true it returns the original value wrapped with Some. + If the predicate returns false, Nothing is returned + + .. code:: python + >>> from returns.maybe import Maybe, Nothing, Some + + >>> assert Some(5).filter(lambda x: x % 2 == 0) == Nothing + >>> assert Some(6).filter(lambda x: x % 2 == 0) == Some(6) + >>> assert Nothing.filter(lambda x: True) == Nothing + """ def lash( self, function: Callable[[Any], Kind1['Maybe', _ValueType]], @@ -338,6 +352,10 @@ def bind_optional(self, function): """Does nothing.""" return self + def filter(self, function): + """Does nothing for ``Nothing`` """ + return self + def lash(self, function): """Composes this container with a function returning container.""" return function(None) @@ -414,6 +432,12 @@ def failure(self): """Raises exception for successful container.""" raise UnwrapFailedError(self) + def filter(self, function): + if function(self._inner_value): + return self + else: + return _Nothing() + Maybe.success_type = Some Maybe.failure_type = _Nothing diff --git a/tests/test_maybe/test_maybe_filter.py b/tests/test_maybe/test_maybe_filter.py new file mode 100644 index 000000000..7132ec562 --- /dev/null +++ b/tests/test_maybe/test_maybe_filter.py @@ -0,0 +1,10 @@ +from returns.maybe import Maybe, Nothing, Some + + +def test_maybe_filter(): + def predicate(value): + return value % 2 == 0 + + assert Some(5).filter(predicate) == Nothing + assert Some(6).filter(predicate) == Some(6) + assert Nothing.filter(predicate) == Nothing From 586af8bff072a31c90ee48fe5d6fbf4e7c4c6904 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Gutowski?= Date: Tue, 16 Nov 2021 20:57:41 +0100 Subject: [PATCH 02/10] add changes to changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4851b782d..67a06ee8d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ Versions before `1.0.0` are `0Ver`-based: incremental in minor, bugfixes only are patches. See [0Ver](https://0ver.org/). +## 0.17.2 + +### Features +- Add `filter` for `Maybe` + ## 0.17.1 ### Bugfixes From 6b96d215bd8a4bf2d2cd2741a7193c2b6a98006a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Gutowski?= Date: Tue, 16 Nov 2021 21:05:38 +0100 Subject: [PATCH 03/10] Update docs --- returns/maybe.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/returns/maybe.py b/returns/maybe.py index 9174f9a82..0af96a3c2 100644 --- a/returns/maybe.py +++ b/returns/maybe.py @@ -149,11 +149,12 @@ def filter(self, If the predicate returns false, Nothing is returned .. code:: python - >>> from returns.maybe import Maybe, Nothing, Some + >>> def predicate(value): + >>> return value % 2 == 0 - >>> assert Some(5).filter(lambda x: x % 2 == 0) == Nothing - >>> assert Some(6).filter(lambda x: x % 2 == 0) == Some(6) - >>> assert Nothing.filter(lambda x: True) == Nothing + >>> assert Some(5).filter(predicate) == Nothing + >>> assert Some(6).filter(predicate) == Some(6) + >>> assert Nothing.filter(predicate) == Nothing """ def lash( self, From e281d41cceabadc0b863d88ddeaa10271c323a15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Gutowski?= Date: Tue, 16 Nov 2021 21:08:58 +0100 Subject: [PATCH 04/10] Update docs2 --- returns/maybe.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/returns/maybe.py b/returns/maybe.py index 0af96a3c2..7fbf44842 100644 --- a/returns/maybe.py +++ b/returns/maybe.py @@ -149,8 +149,10 @@ def filter(self, If the predicate returns false, Nothing is returned .. code:: python + + >>> from returns.maybe import Maybe, Some, Nothing >>> def predicate(value): - >>> return value % 2 == 0 + ... return value % 2 == 0 >>> assert Some(5).filter(predicate) == Nothing >>> assert Some(6).filter(predicate) == Some(6) From bed806ea400b5402509d91b725f6e1134a57ed17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Gutowski?= Date: Thu, 18 Nov 2021 07:50:33 +0100 Subject: [PATCH 05/10] hkt for filter --- returns/interfaces/filterable.py | 41 +++++++++++++++++++++++++++ returns/maybe.py | 24 ++++++++++------ returns/pointfree/__init__.py | 1 + returns/pointfree/filter.py | 39 +++++++++++++++++++++++++ tests/test_maybe/test_maybe_filter.py | 13 +++++---- 5 files changed, 104 insertions(+), 14 deletions(-) create mode 100644 returns/interfaces/filterable.py create mode 100644 returns/pointfree/filter.py diff --git a/returns/interfaces/filterable.py b/returns/interfaces/filterable.py new file mode 100644 index 000000000..4998f44cc --- /dev/null +++ b/returns/interfaces/filterable.py @@ -0,0 +1,41 @@ +from abc import abstractmethod +from typing import Callable, Generic, TypeVar + +from returns.primitives.hkt import Kind1 + +_InnerType = TypeVar('_InnerType') + +_FilterableType = TypeVar('_FilterableType', bound='Filterable') + + +class Filterable(Generic[_InnerType]): + """ + Represents container that can apply filter over inner value. + + There are no aliases or ``FilterableN` for ``Filterable`` interface. + Because it always uses one type. + + Not all types can be ``Filterable`` because we require + a possibility to access internal value and to model a case, + where the predicate is false + + .. code:: python + + >>> from returns.maybe import Nothing, Some + >>> from returns.pointfree import filter_ + + >>> def example(argument: int) -> bool: + ... return argument % 2 == 0 + + >>> assert filter_(example)(Some(5)) == Nothing + >>> assert filter_(example)(Some(6)) == Some(6) + >>> assert filter_(example)(Nothing) == Nothing + + """ + + @abstractmethod + def filter( + self: _FilterableType, + predicate: Callable[[_InnerType], bool], + ) -> Kind1[_FilterableType, _InnerType]: + """Applies 'predicate' to the result fo a previous computation.""" diff --git a/returns/maybe.py b/returns/maybe.py index 7fbf44842..07dbe7ecd 100644 --- a/returns/maybe.py +++ b/returns/maybe.py @@ -141,23 +141,29 @@ def bind_optional( """ - def filter(self, - function: Callable[[_ValueType], bool], - ) -> 'Maybe[_ValueType]': + def filter( + self, + function: Callable[[_ValueType], bool], + ) -> 'Maybe[_ValueType]': """ - Apply a predicate over the value. If the predicate returns true it returns the original value wrapped with Some. + Apply a predicate over the value. + + If the predicate returns true, + it returns the original value wrapped with Some. If the predicate returns false, Nothing is returned .. code:: python - >>> from returns.maybe import Maybe, Some, Nothing + >>> from returns.maybe import Some, Nothing >>> def predicate(value): ... return value % 2 == 0 >>> assert Some(5).filter(predicate) == Nothing >>> assert Some(6).filter(predicate) == Some(6) >>> assert Nothing.filter(predicate) == Nothing + """ + def lash( self, function: Callable[[Any], Kind1['Maybe', _ValueType]], @@ -356,7 +362,7 @@ def bind_optional(self, function): return self def filter(self, function): - """Does nothing for ``Nothing`` """ + """Does nothing.""" return self def lash(self, function): @@ -436,10 +442,10 @@ def failure(self): raise UnwrapFailedError(self) def filter(self, function): + """Filters internal value.""" if function(self._inner_value): return self - else: - return _Nothing() + return _Nothing() Maybe.success_type = Some @@ -475,7 +481,9 @@ def maybe( Requires our :ref:`mypy plugin `. """ + @wraps(function) def decorator(*args, **kwargs): return Maybe.from_optional(function(*args, **kwargs)) + return decorator diff --git a/returns/pointfree/__init__.py b/returns/pointfree/__init__.py index bbece84cc..bd0105596 100644 --- a/returns/pointfree/__init__.py +++ b/returns/pointfree/__init__.py @@ -35,6 +35,7 @@ from returns.pointfree.bind_result import bind_result as bind_result from returns.pointfree.compose_result import compose_result as compose_result from returns.pointfree.cond import cond as cond +from returns.pointfree.filter import filter_ as filter_ from returns.pointfree.lash import lash as lash from returns.pointfree.map import map_ as map_ from returns.pointfree.modify_env import modify_env as modify_env diff --git a/returns/pointfree/filter.py b/returns/pointfree/filter.py new file mode 100644 index 000000000..11f571b63 --- /dev/null +++ b/returns/pointfree/filter.py @@ -0,0 +1,39 @@ +from typing import Callable, TypeVar + +from returns.interfaces.filterable import Filterable +from returns.primitives.hkt import Kind1, Kinded, kinded + +_InnerType = TypeVar('_InnerType') +_FilterableKind = TypeVar('_FilterableKind', bound=Filterable) + + +def filter_( + predicate: Callable[[_InnerType], bool], +) -> Kinded[Callable[ + [Kind1[_FilterableKind, _InnerType]], + Kind1[_FilterableKind, _InnerType], +]]: + """ + Applies predicate over container. + + This is how it should be used: + + .. code:: python + + >>> from returns.maybe import Some, Nothing + + >>> def example(value): + ... return value % 2 == 0 + + >>> assert filter_(example)(Some(5)) == Nothing + >>> assert filter_(example)(Some(6)) == Some(6) + + """ + + @kinded + def factory( + container: Kind1[_FilterableKind, _InnerType], + ) -> Kind1[_FilterableKind, _InnerType]: + return container.filter(predicate) + + return factory diff --git a/tests/test_maybe/test_maybe_filter.py b/tests/test_maybe/test_maybe_filter.py index 7132ec562..d24bb7fb5 100644 --- a/tests/test_maybe/test_maybe_filter.py +++ b/tests/test_maybe/test_maybe_filter.py @@ -1,10 +1,11 @@ -from returns.maybe import Maybe, Nothing, Some +from returns.maybe import Nothing, Some def test_maybe_filter(): - def predicate(value): - return value % 2 == 0 + """Ensures that .filter works correctly.""" + def factory(argument: int) -> bool: + return argument % 2 == 0 - assert Some(5).filter(predicate) == Nothing - assert Some(6).filter(predicate) == Some(6) - assert Nothing.filter(predicate) == Nothing + assert Some(5).filter(factory) == Nothing + assert Some(6).filter(factory) == Some(6) + assert Nothing.filter(factory) == Nothing From 4954a4a6097c20dd9acb3c75d93813fcefdbcac8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Gutowski?= Date: Sat, 20 Nov 2021 12:23:09 +0100 Subject: [PATCH 06/10] Bind filter to maybe n --- returns/interfaces/filterable.py | 25 +++++++++++++++++++------ returns/pointfree/filter.py | 20 +++++++++++--------- 2 files changed, 30 insertions(+), 15 deletions(-) diff --git a/returns/interfaces/filterable.py b/returns/interfaces/filterable.py index 4998f44cc..a85e65a9c 100644 --- a/returns/interfaces/filterable.py +++ b/returns/interfaces/filterable.py @@ -1,14 +1,17 @@ from abc import abstractmethod -from typing import Callable, Generic, TypeVar +from typing import Callable, NoReturn, TypeVar +from returns.interfaces.specific.maybe import MaybeLikeN from returns.primitives.hkt import Kind1 -_InnerType = TypeVar('_InnerType') +_FirstType = TypeVar('_FirstType') +_SecondType = TypeVar('_SecondType') +_ThirdType = TypeVar('_ThirdType') -_FilterableType = TypeVar('_FilterableType', bound='Filterable') +_FilterableType = TypeVar('_FilterableType', bound='FilterableN') -class Filterable(Generic[_InnerType]): +class FilterableN(MaybeLikeN[_FirstType, _SecondType, _ThirdType]): """ Represents container that can apply filter over inner value. @@ -36,6 +39,16 @@ class Filterable(Generic[_InnerType]): @abstractmethod def filter( self: _FilterableType, - predicate: Callable[[_InnerType], bool], - ) -> Kind1[_FilterableType, _InnerType]: + predicate: Callable[[_FirstType], bool], + ) -> Kind1[_FilterableType, _FirstType]: """Applies 'predicate' to the result fo a previous computation.""" + + +#: Type alias for kinds with one type argument. +Filterable1 = FilterableN[_FirstType, NoReturn, NoReturn] + +#: Type alias for kinds with two type arguments. +Filterable2 = FilterableN[_FirstType, _SecondType, NoReturn] + +#: Type alias for kinds with three type arguments. +Filterable3 = FilterableN[_FirstType, _SecondType, _ThirdType] diff --git a/returns/pointfree/filter.py b/returns/pointfree/filter.py index 11f571b63..03d37e364 100644 --- a/returns/pointfree/filter.py +++ b/returns/pointfree/filter.py @@ -1,17 +1,19 @@ from typing import Callable, TypeVar -from returns.interfaces.filterable import Filterable -from returns.primitives.hkt import Kind1, Kinded, kinded +from returns.interfaces.filterable import FilterableN +from returns.primitives.hkt import Kinded, KindN, kinded -_InnerType = TypeVar('_InnerType') -_FilterableKind = TypeVar('_FilterableKind', bound=Filterable) +_FirstType = TypeVar('_FirstType') +_SecondType = TypeVar('_SecondType') +_ThirdType = TypeVar('_ThirdType') +_FilterableKind = TypeVar('_FilterableKind', bound=FilterableN) def filter_( - predicate: Callable[[_InnerType], bool], + predicate: Callable[[_FirstType], bool], ) -> Kinded[Callable[ - [Kind1[_FilterableKind, _InnerType]], - Kind1[_FilterableKind, _InnerType], + [KindN[_FilterableKind, _FirstType, _SecondType, _ThirdType]], + KindN[_FilterableKind, _FirstType, _SecondType, _ThirdType], ]]: """ Applies predicate over container. @@ -32,8 +34,8 @@ def filter_( @kinded def factory( - container: Kind1[_FilterableKind, _InnerType], - ) -> Kind1[_FilterableKind, _InnerType]: + container: KindN[_FilterableKind, _FirstType, _SecondType, _ThirdType], + ) -> KindN[_FilterableKind, _FirstType, _SecondType, _ThirdType]: return container.filter(predicate) return factory From 45b1fa6666a27197e2a427ebc3649747aac46165 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Gutowski?= Date: Tue, 23 Nov 2021 20:20:28 +0100 Subject: [PATCH 07/10] satisfy almighty mypy --- tests/test_maybe/test_maybe_filter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_maybe/test_maybe_filter.py b/tests/test_maybe/test_maybe_filter.py index d24bb7fb5..0254a3fd7 100644 --- a/tests/test_maybe/test_maybe_filter.py +++ b/tests/test_maybe/test_maybe_filter.py @@ -3,7 +3,7 @@ def test_maybe_filter(): """Ensures that .filter works correctly.""" - def factory(argument: int) -> bool: + def factory(argument): return argument % 2 == 0 assert Some(5).filter(factory) == Nothing From 38781849dea728cfe692a4d1410c2a6d2e69057c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Gutowski?= Date: Sun, 28 Nov 2021 14:57:28 +0100 Subject: [PATCH 08/10] typo and nested function typing --- returns/interfaces/filterable.py | 10 +++++----- tests/test_maybe/test_maybe_filter.py | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/returns/interfaces/filterable.py b/returns/interfaces/filterable.py index a85e65a9c..92ba7f9e2 100644 --- a/returns/interfaces/filterable.py +++ b/returns/interfaces/filterable.py @@ -27,12 +27,12 @@ class FilterableN(MaybeLikeN[_FirstType, _SecondType, _ThirdType]): >>> from returns.maybe import Nothing, Some >>> from returns.pointfree import filter_ - >>> def example(argument: int) -> bool: + >>> def is_even(argument: int) -> bool: ... return argument % 2 == 0 - >>> assert filter_(example)(Some(5)) == Nothing - >>> assert filter_(example)(Some(6)) == Some(6) - >>> assert filter_(example)(Nothing) == Nothing + >>> assert filter_(is_even)(Some(5)) == Nothing + >>> assert filter_(is_even)(Some(6)) == Some(6) + >>> assert filter_(is_even)(Nothing) == Nothing """ @@ -41,7 +41,7 @@ def filter( self: _FilterableType, predicate: Callable[[_FirstType], bool], ) -> Kind1[_FilterableType, _FirstType]: - """Applies 'predicate' to the result fo a previous computation.""" + """Applies 'predicate' to the result of a previous computation.""" #: Type alias for kinds with one type argument. diff --git a/tests/test_maybe/test_maybe_filter.py b/tests/test_maybe/test_maybe_filter.py index 0254a3fd7..d24bb7fb5 100644 --- a/tests/test_maybe/test_maybe_filter.py +++ b/tests/test_maybe/test_maybe_filter.py @@ -3,7 +3,7 @@ def test_maybe_filter(): """Ensures that .filter works correctly.""" - def factory(argument): + def factory(argument: int) -> bool: return argument % 2 == 0 assert Some(5).filter(factory) == Nothing From 60950f6404307bb38ec54018db2cc785fa01a859 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Gutowski?= Date: Sat, 11 Dec 2021 22:17:18 +0100 Subject: [PATCH 09/10] issue found! --- returns/maybe.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/returns/maybe.py b/returns/maybe.py index 07dbe7ecd..c0d591baa 100644 --- a/returns/maybe.py +++ b/returns/maybe.py @@ -402,7 +402,7 @@ def __init__(self, inner_value: _ValueType) -> None: """Some constructor.""" super().__init__(inner_value) - if not TYPE_CHECKING: # noqa: WPS604 # pragma: no branch + if not TYPE_CHECKING: # noqa: WPS604,C901 # pragma: no branch def bind(self, function): """Binds current container to a function that returns container.""" return function(self._inner_value) @@ -415,6 +415,12 @@ def unwrap(self): """Returns inner value for successful container.""" return self._inner_value + def filter(self, function): + """Filters internal value.""" + if function(self._inner_value): + return self + return _Nothing() + def map(self, function): """Composes current container with a pure function.""" return Some(function(self._inner_value)) @@ -441,12 +447,6 @@ def failure(self): """Raises exception for successful container.""" raise UnwrapFailedError(self) - def filter(self, function): - """Filters internal value.""" - if function(self._inner_value): - return self - return _Nothing() - Maybe.success_type = Some Maybe.failure_type = _Nothing From cca6894ee3f347ccb0419443358ccf25c161ac94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Gutowski?= Date: Sun, 12 Dec 2021 09:28:21 +0100 Subject: [PATCH 10/10] slots added --- returns/interfaces/filterable.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/returns/interfaces/filterable.py b/returns/interfaces/filterable.py index 92ba7f9e2..e445ea4c8 100644 --- a/returns/interfaces/filterable.py +++ b/returns/interfaces/filterable.py @@ -36,6 +36,8 @@ class FilterableN(MaybeLikeN[_FirstType, _SecondType, _ThirdType]): """ + __slots__ = () + @abstractmethod def filter( self: _FilterableType,