Skip to content

Commit bed806e

Browse files
hkt for filter
1 parent e281d41 commit bed806e

File tree

5 files changed

+104
-14
lines changed

5 files changed

+104
-14
lines changed

returns/interfaces/filterable.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
from abc import abstractmethod
2+
from typing import Callable, Generic, TypeVar
3+
4+
from returns.primitives.hkt import Kind1
5+
6+
_InnerType = TypeVar('_InnerType')
7+
8+
_FilterableType = TypeVar('_FilterableType', bound='Filterable')
9+
10+
11+
class Filterable(Generic[_InnerType]):
12+
"""
13+
Represents container that can apply filter over inner value.
14+
15+
There are no aliases or ``FilterableN` for ``Filterable`` interface.
16+
Because it always uses one type.
17+
18+
Not all types can be ``Filterable`` because we require
19+
a possibility to access internal value and to model a case,
20+
where the predicate is false
21+
22+
.. code:: python
23+
24+
>>> from returns.maybe import Nothing, Some
25+
>>> from returns.pointfree import filter_
26+
27+
>>> def example(argument: int) -> bool:
28+
... return argument % 2 == 0
29+
30+
>>> assert filter_(example)(Some(5)) == Nothing
31+
>>> assert filter_(example)(Some(6)) == Some(6)
32+
>>> assert filter_(example)(Nothing) == Nothing
33+
34+
"""
35+
36+
@abstractmethod
37+
def filter(
38+
self: _FilterableType,
39+
predicate: Callable[[_InnerType], bool],
40+
) -> Kind1[_FilterableType, _InnerType]:
41+
"""Applies 'predicate' to the result fo a previous computation."""

returns/maybe.py

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -141,23 +141,29 @@ def bind_optional(
141141
142142
"""
143143

144-
def filter(self,
145-
function: Callable[[_ValueType], bool],
146-
) -> 'Maybe[_ValueType]':
144+
def filter(
145+
self,
146+
function: Callable[[_ValueType], bool],
147+
) -> 'Maybe[_ValueType]':
147148
"""
148-
Apply a predicate over the value. If the predicate returns true it returns the original value wrapped with Some.
149+
Apply a predicate over the value.
150+
151+
If the predicate returns true,
152+
it returns the original value wrapped with Some.
149153
If the predicate returns false, Nothing is returned
150154
151155
.. code:: python
152156
153-
>>> from returns.maybe import Maybe, Some, Nothing
157+
>>> from returns.maybe import Some, Nothing
154158
>>> def predicate(value):
155159
... return value % 2 == 0
156160
157161
>>> assert Some(5).filter(predicate) == Nothing
158162
>>> assert Some(6).filter(predicate) == Some(6)
159163
>>> assert Nothing.filter(predicate) == Nothing
164+
160165
"""
166+
161167
def lash(
162168
self,
163169
function: Callable[[Any], Kind1['Maybe', _ValueType]],
@@ -356,7 +362,7 @@ def bind_optional(self, function):
356362
return self
357363

358364
def filter(self, function):
359-
"""Does nothing for ``Nothing`` """
365+
"""Does nothing."""
360366
return self
361367

362368
def lash(self, function):
@@ -436,10 +442,10 @@ def failure(self):
436442
raise UnwrapFailedError(self)
437443

438444
def filter(self, function):
445+
"""Filters internal value."""
439446
if function(self._inner_value):
440447
return self
441-
else:
442-
return _Nothing()
448+
return _Nothing()
443449

444450

445451
Maybe.success_type = Some
@@ -475,7 +481,9 @@ def maybe(
475481
Requires our :ref:`mypy plugin <mypy-plugins>`.
476482
477483
"""
484+
478485
@wraps(function)
479486
def decorator(*args, **kwargs):
480487
return Maybe.from_optional(function(*args, **kwargs))
488+
481489
return decorator

returns/pointfree/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
from returns.pointfree.bind_result import bind_result as bind_result
3636
from returns.pointfree.compose_result import compose_result as compose_result
3737
from returns.pointfree.cond import cond as cond
38+
from returns.pointfree.filter import filter_ as filter_
3839
from returns.pointfree.lash import lash as lash
3940
from returns.pointfree.map import map_ as map_
4041
from returns.pointfree.modify_env import modify_env as modify_env

returns/pointfree/filter.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
from typing import Callable, TypeVar
2+
3+
from returns.interfaces.filterable import Filterable
4+
from returns.primitives.hkt import Kind1, Kinded, kinded
5+
6+
_InnerType = TypeVar('_InnerType')
7+
_FilterableKind = TypeVar('_FilterableKind', bound=Filterable)
8+
9+
10+
def filter_(
11+
predicate: Callable[[_InnerType], bool],
12+
) -> Kinded[Callable[
13+
[Kind1[_FilterableKind, _InnerType]],
14+
Kind1[_FilterableKind, _InnerType],
15+
]]:
16+
"""
17+
Applies predicate over container.
18+
19+
This is how it should be used:
20+
21+
.. code:: python
22+
23+
>>> from returns.maybe import Some, Nothing
24+
25+
>>> def example(value):
26+
... return value % 2 == 0
27+
28+
>>> assert filter_(example)(Some(5)) == Nothing
29+
>>> assert filter_(example)(Some(6)) == Some(6)
30+
31+
"""
32+
33+
@kinded
34+
def factory(
35+
container: Kind1[_FilterableKind, _InnerType],
36+
) -> Kind1[_FilterableKind, _InnerType]:
37+
return container.filter(predicate)
38+
39+
return factory

tests/test_maybe/test_maybe_filter.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
from returns.maybe import Maybe, Nothing, Some
1+
from returns.maybe import Nothing, Some
22

33

44
def test_maybe_filter():
5-
def predicate(value):
6-
return value % 2 == 0
5+
"""Ensures that .filter works correctly."""
6+
def factory(argument: int) -> bool:
7+
return argument % 2 == 0
78

8-
assert Some(5).filter(predicate) == Nothing
9-
assert Some(6).filter(predicate) == Some(6)
10-
assert Nothing.filter(predicate) == Nothing
9+
assert Some(5).filter(factory) == Nothing
10+
assert Some(6).filter(factory) == Some(6)
11+
assert Nothing.filter(factory) == Nothing

0 commit comments

Comments
 (0)