-
-
Notifications
You must be signed in to change notification settings - Fork 123
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
119b081
commit f589867
Showing
11 changed files
with
430 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
.. transducers: | ||
Transducers | ||
=========== | ||
|
||
API Reference | ||
------------- | ||
|
||
.. automodule:: returns.transducers.transducers | ||
:members: | ||
|
||
.. autofunction:: returns.transducers.tmap | ||
|
||
.. autofunction:: returns.transducers.tfilter |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
from returns.transducers.tfilter import tfilter as tfilter | ||
from returns.transducers.tmap import tmap as tmap | ||
from returns.transducers.transducers import Missing as Missing | ||
from returns.transducers.transducers import Reduced as Reduced | ||
from returns.transducers.transducers import transduce as transduce | ||
from returns.transducers.transducers import treduce as treduce |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
from typing import Callable, TypeVar | ||
|
||
_ValueType = TypeVar('_ValueType') | ||
_AccValueType = TypeVar('_AccValueType') | ||
|
||
|
||
def tfilter( | ||
predicate: Callable[[_ValueType], bool], | ||
) -> Callable[ | ||
[Callable[[_AccValueType, _ValueType], _AccValueType]], | ||
Callable[[_AccValueType, _ValueType], _AccValueType], | ||
]: | ||
""" | ||
:py:func:`filter <filter>` implementation on a transducer form. | ||
.. code:: python | ||
>>> from typing import List | ||
>>> from returns.transducers import tfilter, treduce | ||
>>> def is_even(number: int) -> bool: | ||
... return number % 2 == 0 | ||
>>> def append(collection: List[int], item: int) -> List[int]: | ||
... collection.append(item) | ||
... return collection | ||
>>> my_list = [0, 1, 2, 3, 4, 5, 6] | ||
>>> xform = tfilter(is_even)(append) | ||
>>> assert treduce(xform, my_list, []) == [0, 2, 4, 6] | ||
""" | ||
def reducer( | ||
step: Callable[[_AccValueType, _ValueType], _AccValueType], | ||
) -> Callable[[_AccValueType, _ValueType], _AccValueType]: | ||
def filter_(acc: _AccValueType, value: _ValueType) -> _AccValueType: | ||
if predicate(value): | ||
return step(acc, value) | ||
return acc | ||
return filter_ | ||
return reducer |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
from typing import Callable, TypeVar | ||
|
||
_ValueType = TypeVar('_ValueType') | ||
_NewValueType = TypeVar('_NewValueType') | ||
|
||
_AccValueType = TypeVar('_AccValueType') | ||
|
||
|
||
def tmap( | ||
function: Callable[[_ValueType], _NewValueType], | ||
) -> Callable[ | ||
[Callable[[_AccValueType, _NewValueType], _AccValueType]], | ||
Callable[[_AccValueType, _ValueType], _AccValueType], | ||
]: | ||
""" | ||
A map implementation on a transducer form. | ||
.. code:: python | ||
>>> from typing import List | ||
>>> from returns.transducers import tmap, treduce | ||
>>> def add_one(number: int) -> int: | ||
... return number + 1 | ||
>>> def append(collection: List[int], item: int) -> List[int]: | ||
... collection.append(item) | ||
... return collection | ||
>>> my_list = [0, 1] | ||
>>> xformaa = tmap(add_one)(append) | ||
>>> assert treduce(xformaa, my_list, []) == [1, 2] | ||
""" | ||
def reducer( | ||
step: Callable[[_AccValueType, _NewValueType], _AccValueType], | ||
) -> Callable[[_AccValueType, _ValueType], _AccValueType]: | ||
def map_(acc: _AccValueType, value: _ValueType) -> _AccValueType: | ||
return step(acc, function(value)) | ||
return map_ | ||
return reducer |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
from typing import ( | ||
Any, | ||
Callable, | ||
Generic, | ||
Iterable, | ||
Optional, | ||
TypeVar, | ||
final, | ||
overload, | ||
) | ||
|
||
from returns.primitives.types import Immutable | ||
|
||
_ValueType = TypeVar('_ValueType') | ||
_NewValueType = TypeVar('_NewValueType') | ||
|
||
_AccValueType = TypeVar('_AccValueType') | ||
|
||
|
||
@final | ||
class Reduced(Immutable, Generic[_ValueType]): | ||
""" | ||
Sentinel for early termination inside transducer. | ||
.. code:: python | ||
>>> from returns.transducers import tmap, transduce, Reduced | ||
>>> def add_one(number: int) -> int: | ||
... return number + 1 | ||
>>> def add(acc: int, number: int) -> int: | ||
... if acc == 3: | ||
... return Reduced(acc) | ||
... return acc + number | ||
>>> my_list = [0, 1, 2] | ||
>>> assert transduce(tmap(add_one), add, 0, my_list) == 3 | ||
""" | ||
|
||
__slots__ = ('_inner_value',) | ||
|
||
_inner_value: _ValueType | ||
|
||
def __init__(self, inner_value: _ValueType) -> None: | ||
"""Encapsulates the value from early reduce termination.""" | ||
object.__setattr__(self, '_inner_value', inner_value) # noqa: WPS609 | ||
|
||
@property | ||
def value(self) -> _ValueType: # noqa: WPS110 | ||
"""Returns the value from early reduce termination.""" | ||
return self._inner_value | ||
|
||
|
||
@final | ||
class _Missing(Immutable): | ||
"""Represents a missing value for reducers.""" | ||
|
||
__slots__ = ('_instance',) | ||
|
||
_instance: Optional['_Missing'] = None | ||
|
||
def __new__(cls, *args: Any, **kwargs: Any) -> '_Missing': | ||
if cls._instance is None: | ||
cls._instance = object.__new__(cls) # noqa: WPS609 | ||
return cls._instance | ||
|
||
|
||
#: A singleton representing any missing value | ||
Missing = _Missing() | ||
|
||
|
||
def transduce( | ||
xform: Callable[ | ||
[Callable[[_AccValueType, _ValueType], _AccValueType]], | ||
Callable[[_AccValueType, _ValueType], _AccValueType], | ||
], | ||
reducing_function: Callable[[_AccValueType, _ValueType], _AccValueType], | ||
initial: _AccValueType, | ||
iterable: Iterable[_ValueType], | ||
) -> _AccValueType: | ||
""" | ||
Process information with transducers. | ||
.. code:: python | ||
>>> from returns.transducers import tmap, transduce | ||
>>> def add_one(number: int) -> int: | ||
... return number + 1 | ||
>>> def add(acc: int, number: int) -> int: | ||
... return acc + number | ||
>>> my_list = [0, 1, 2] | ||
>>> assert transduce(tmap(add_one), add, 0, my_list) == 6 | ||
""" | ||
reducer = xform(reducing_function) | ||
return treduce(reducer, iterable, initial) | ||
|
||
|
||
@overload | ||
def treduce( | ||
function: Callable[[_ValueType, _ValueType], _ValueType], | ||
iterable: Iterable[_ValueType], | ||
initial: _Missing = Missing, | ||
) -> _ValueType: | ||
"""Reduce without an initial value.""" | ||
|
||
|
||
@overload | ||
def treduce( | ||
function: Callable[[_AccValueType, _ValueType], _AccValueType], | ||
iterable: Iterable[_ValueType], | ||
initial: _AccValueType, | ||
) -> _AccValueType: | ||
"""Reduce with an initial value.""" | ||
|
||
|
||
def treduce(function, iterable, initial=Missing): | ||
""" | ||
A rewritten version of :func:`reduce <functools.reduce>`. | ||
This version considers some features borrowed from Clojure: | ||
- Early termination | ||
- Function initializer [TODO] | ||
You can use it as a normal reduce if you want: | ||
.. code:: python | ||
>>> from returns.transducers import treduce | ||
>>> def add(acc: int, value: int) -> int: | ||
... return acc + value | ||
>>> assert treduce(add, [1, 2, 3]) == 6 | ||
""" | ||
it = iter(iterable) | ||
|
||
if initial is Missing: | ||
try: | ||
acc_value = next(it) | ||
except StopIteration: | ||
raise TypeError( | ||
'reduce() of empty iterable with no initial value', | ||
) from None | ||
else: | ||
acc_value = initial | ||
|
||
for value in it: # noqa: WPS110 | ||
acc_value = function(acc_value, value) | ||
if isinstance(acc_value, Reduced): | ||
return acc_value.value | ||
return acc_value |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
6 changes: 6 additions & 0 deletions
6
tests/test_transducers/test_transducers/test_missing_singleton.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
from returns.transducers.transducers import _Missing | ||
|
||
|
||
def test_missing_singleton(): | ||
"""Ensures `_Missing` is a singleton.""" | ||
assert _Missing() is _Missing() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import pytest | ||
|
||
from returns.transducers import treduce | ||
|
||
|
||
def test_reduce(): | ||
"""Should fail when iterable is empty and non initial value is given.""" | ||
with pytest.raises(TypeError): | ||
treduce(lambda acc, value: acc + value, []) # noqa: WPS110 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
- case: tfilter | ||
disable_cache: false | ||
main: | | ||
from returns.transducers import tfilter | ||
def is_even(number: int) -> bool: | ||
... | ||
reveal_type(tfilter(is_even)) # N: Revealed type is 'def [_AccType] (def (_AccType`-2, builtins.int*) -> _AccType`-2) -> def (_AccType`-2, builtins.int*) -> _AccType`-2' | ||
- case: tfilter_reducer | ||
disable_cache: false | ||
main: | | ||
from typing import List | ||
from returns.transducers import tfilter | ||
def is_even(number: int) -> bool: | ||
... | ||
def append(collection: List[int], item: int) -> List[int]: | ||
... | ||
reveal_type(tfilter(is_even)(append)) # N: Revealed type is 'def (builtins.list*[builtins.int], builtins.int) -> builtins.list*[builtins.int]' | ||
- case: tfilter_reducer_filter_ | ||
disable_cache: false | ||
main: | | ||
from typing import List | ||
from returns.transducers import tfilter, reduce | ||
def is_even(number: int) -> bool: | ||
... | ||
def append(collection: List[int], item: int) -> List[int]: | ||
... | ||
my_list: List[int] | ||
reveal_type(tfilter(is_even)(append)(my_list, 2)) # N: Revealed type is 'builtins.list*[builtins.int]' | ||
- case: tfilter_composition_one | ||
disable_cache: false | ||
main: | | ||
from typing import List | ||
from returns.transducers import tfilter, reduce | ||
def is_even(number: int) -> bool: | ||
... | ||
def append(collection: List[int], item: int) -> List[int]: | ||
... | ||
composed = tfilter(is_even)(tfilter(is_even)(append)) | ||
reveal_type(composed) # N: Revealed type is 'def (builtins.list*[builtins.int], builtins.int) -> builtins.list*[builtins.int]' | ||
- case: tfilter_composition_two | ||
disable_cache: false | ||
main: | | ||
from typing import List | ||
from returns.transducers import tfilter, reduce | ||
def is_even(number: int) -> bool: | ||
... | ||
def append(collection: List[int], item: int) -> List[int]: | ||
... | ||
composed = tfilter(is_even)(tfilter(is_even)(append)) | ||
my_list: List[int] | ||
reveal_type(composed(my_list, 42)) # N: Revealed type is 'builtins.list*[builtins.int]' |
Oops, something went wrong.