Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
thepabloaguilar committed Aug 30, 2022
1 parent 119b081 commit f589867
Show file tree
Hide file tree
Showing 11 changed files with 430 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ Contents
pages/functions.rst
pages/curry.rst
pages/types.rst
pages/transducers.rst

.. toctree::
:maxdepth: 2
Expand Down
14 changes: 14 additions & 0 deletions docs/pages/transducers.rst
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
6 changes: 6 additions & 0 deletions returns/transducers/__init__.py
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
41 changes: 41 additions & 0 deletions returns/transducers/tfilter.py
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
41 changes: 41 additions & 0 deletions returns/transducers/tmap.py
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
158 changes: 158 additions & 0 deletions returns/transducers/transducers.py
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
3 changes: 3 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ per-file-ignores =
returns/methods/__init__.py: F401, WPS201
returns/pipeline.py: F401
returns/context/__init__.py: F401, WPS201
returns/transducers/__init__.py: F401
# Disable some quality checks for the most heavy parts:
returns/io.py: WPS402
returns/iterables.py: WPS234
Expand All @@ -77,6 +78,8 @@ per-file-ignores =
returns/primitives/asserts.py: S101
# Some rules cannot be applied to context:
returns/context/*.py: WPS201, WPS204, WPS226, WPS326, WPS430
# Some rules cannot be applied to transducers:
returns/transducers/*.py: WPS110, WPS430
# We allow `futures` to do attribute access:
returns/future.py: WPS437
returns/_internal/futures/*.py: WPS204, WPS433, WPS437
Expand Down
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()
9 changes: 9 additions & 0 deletions tests/test_transducers/test_transducers/test_treduce.py
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
73 changes: 73 additions & 0 deletions typesafety/test_transducers/test_tfilter.yml
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]'
Loading

0 comments on commit f589867

Please sign in to comment.