Skip to content

Commit 4ca8bbf

Browse files
hkt for filter
1 parent dbae925 commit 4ca8bbf

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
@@ -139,23 +139,29 @@ def bind_optional(
139139
140140
"""
141141

142-
def filter(self,
143-
function: Callable[[_ValueType], bool],
144-
) -> 'Maybe[_ValueType]':
142+
def filter(
143+
self,
144+
function: Callable[[_ValueType], bool],
145+
) -> 'Maybe[_ValueType]':
145146
"""
146-
Apply a predicate over the value. If the predicate returns true it returns the original value wrapped with Some.
147+
Apply a predicate over the value.
148+
149+
If the predicate returns true,
150+
it returns the original value wrapped with Some.
147151
If the predicate returns false, Nothing is returned
148152
149153
.. code:: python
150154
151-
>>> from returns.maybe import Maybe, Some, Nothing
155+
>>> from returns.maybe import Some, Nothing
152156
>>> def predicate(value):
153157
... return value % 2 == 0
154158
155159
>>> assert Some(5).filter(predicate) == Nothing
156160
>>> assert Some(6).filter(predicate) == Some(6)
157161
>>> assert Nothing.filter(predicate) == Nothing
162+
158163
"""
164+
159165
def lash(
160166
self,
161167
function: Callable[[Any], Kind1['Maybe', _ValueType]],
@@ -352,7 +358,7 @@ def bind_optional(self, function):
352358
return self
353359

354360
def filter(self, function):
355-
"""Does nothing for ``Nothing`` """
361+
"""Does nothing."""
356362
return self
357363

358364
def lash(self, function):
@@ -430,10 +436,10 @@ def failure(self):
430436
raise UnwrapFailedError(self)
431437

432438
def filter(self, function):
439+
"""Filters internal value."""
433440
if function(self._inner_value):
434441
return self
435-
else:
436-
return _Nothing()
442+
return _Nothing()
437443

438444

439445
Maybe.success_type = Some
@@ -469,7 +475,9 @@ def maybe(
469475
Requires our :ref:`mypy plugin <mypy-plugins>`.
470476
471477
"""
478+
472479
@wraps(function)
473480
def decorator(*args, **kwargs):
474481
return Maybe.from_optional(function(*args, **kwargs))
482+
475483
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
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)