We have several utility types that we use for our containers, that can also help end users as well.
You can use all power of declarative loops in your app with Fold
.
>>> from returns.iterables import Fold
>>> from returns.io import IO
>>> items = [IO(1), IO(2), IO(3)]
>>> assert Fold.loop(
... items,
... IO(''),
... lambda num: lambda text: text + str(num),
... ) == IO('123')
There are also other helpful methods as well.
See returns.iterables.AbstractFold
.
We also ship AbstractFold
,
where you can change how loop
(or any other) method works.
For example, for performance reasons.
Let’s say you have a big number of
RequiresContext
instances
and you want to do the same thing string concatenation we have shown above.
You might face recursion problems with it:
>>> import sys
>>> from returns.context import Reader
>>> from returns.iterables import Fold
>>> items = [Reader.from_value(num) for num in range(sys.getrecursionlimit())]
>>> Fold.loop(items, Reader.from_value(0), lambda x: lambda y: x + y)(...)
Traceback (most recent call last):
...
RecursionError: ...
So, let’s change how it works for this specific type:
>>> from returns.iterables import AbstractFold
>>> class ContextAwareFold(AbstractFold):
... @classmethod
... def _loop(cls, iterable, acc, function, concat, deps=None):
... wrapped = acc.from_value(function)
... for current in iterable:
... assert isinstance(current, Reader)
... acc = Reader.from_value(concat(current, acc, wrapped)(deps))
... return acc
Note
Don’t forget to add typing annotations to your real code! This is just an example.
And now let’s test that it works without recursion:
>>> items = [Reader.from_value(num) for num in range(sys.getrecursionlimit())]
>>> assert ContextAwareFold.loop(
... items, Reader.from_value(0), lambda x: lambda y: x + y,
... )(...) == sum(range(sys.getrecursionlimit()))
And no error will be produced! We now don’t use recursion inside. Consider this way of doing things as a respected hack.
This class is useful when you need to make some instances immutable (like our containers are immutable).
Bases: object
A collection of different helpers to write declarative Iterable
actions.
Allows to work with iterables.
Implementation
AbstractFold
and Fold
types are special.
They have double definition for each method: public and protected ones.
Why?
Because you cannot override @kinded
method due to a mypy
bug.
So, there are two opportunities for us here:
Declare all method as @final
and do not allow to change anything
Use delegation to protected unkinded methods
We have chosen the second way! Here’s how it works:
Public methods are @kinded
for better typing and cannot be overridden
Protected methods are unkinded and can be overridden in subtyping
Now, if you need to make a change into our implementation,
then you can subclass Fold
or AbstractFold
and then
change an implementation of any unkinded protected method.
Allows to make declarative loops for any ApplicativeN
subtypes.
Quick example:
>>> from typing import Callable
>>> from returns.maybe import Some
>>> from returns.iterables import Fold
>>> def sum_two(first: int) -> Callable[[int], int]:
... return lambda second: first + second
>>> assert Fold.loop(
... [Some(1), Some(2), Some(3)],
... Some(10),
... sum_two,
... ) == Some(16)
Looks like foldl
in some other languages with some more specifics.
See: https://philipschwarz.dev/fpilluminated/?page_id=348#bwg3/137
Is also quite similar to reduce
.
Public interface for _loop
method. Cannot be modified directly.
iterable (Iterable
[KindN
[TypeVar
(_ApplicativeKind
, bound= ApplicativeN
), TypeVar
(_FirstType
), TypeVar
(_SecondType
), TypeVar
(_ThirdType
)]]) –
acc (KindN
[TypeVar
(_ApplicativeKind
, bound= ApplicativeN
), TypeVar
(_UpdatedType
), TypeVar
(_SecondType
), TypeVar
(_ThirdType
)]) –
function (Callable
[[TypeVar
(_FirstType
)], Callable
[[TypeVar
(_UpdatedType
)], TypeVar
(_UpdatedType
)]]) –
KindN
[TypeVar
(_ApplicativeKind
, bound= ApplicativeN
), TypeVar
(_UpdatedType
), TypeVar
(_SecondType
), TypeVar
(_ThirdType
)]
Transforms an iterable of containers into a single container.
Quick example for regular containers:
>>> from returns.io import IO
>>> from returns.iterables import Fold
>>> items = [IO(1), IO(2)]
>>> assert Fold.collect(items, IO(())) == IO((1, 2))
If container can have failed values, then this strategy fails on any existing failed like type.
It is enough to have even a single failed value in iterable for this type to convert the whole operation result to be a failure. Let’s see how it works:
>>> from returns.result import Success, Failure
>>> from returns.iterables import Fold
>>> empty = []
>>> all_success = [Success(1), Success(2), Success(3)]
>>> has_failure = [Success(1), Failure('a'), Success(3)]
>>> all_failures = [Failure('a'), Failure('b')]
>>> acc = Success(()) # empty tuple
>>> assert Fold.collect(empty, acc) == Success(())
>>> assert Fold.collect(all_success, acc) == Success((1, 2, 3))
>>> assert Fold.collect(has_failure, acc) == Failure('a')
>>> assert Fold.collect(all_failures, acc) == Failure('a')
If that’s now what you need, check out
collect_all()
to force collect all non-failed values.
Public interface for _collect
method. Cannot be modified directly.
iterable (Iterable
[KindN
[TypeVar
(_ApplicativeKind
, bound= ApplicativeN
), TypeVar
(_FirstType
), TypeVar
(_SecondType
), TypeVar
(_ThirdType
)]]) –
acc (KindN
[TypeVar
(_ApplicativeKind
, bound= ApplicativeN
), Tuple
[TypeVar
(_FirstType
), ...
], TypeVar
(_SecondType
), TypeVar
(_ThirdType
)]) –
KindN
[TypeVar
(_ApplicativeKind
, bound= ApplicativeN
), Tuple
[TypeVar
(_FirstType
), ...
], TypeVar
(_SecondType
), TypeVar
(_ThirdType
)]
Transforms an iterable of containers into a single container.
This method only works with FailableN
subtypes,
not just any ApplicativeN
like collect()
.
Strategy to extract all successful values even if there are failed values.
If there’s at least one successful value and any amount of failed values, we will still return all collected successful values.
We can return failed value for this strategy only in a single case: when default element is a failed value.
Let’s see how it works:
>>> from returns.result import Success, Failure
>>> from returns.iterables import Fold
>>> empty = []
>>> all_success = [Success(1), Success(2), Success(3)]
>>> has_failure = [Success(1), Failure('a'), Success(3)]
>>> all_failures = [Failure('a'), Failure('b')]
>>> acc = Success(()) # empty tuple
>>> assert Fold.collect_all(empty, acc) == Success(())
>>> assert Fold.collect_all(all_success, acc) == Success((1, 2, 3))
>>> assert Fold.collect_all(has_failure, acc) == Success((1, 3))
>>> assert Fold.collect_all(all_failures, acc) == Success(())
>>> assert Fold.collect_all(empty, Failure('c')) == Failure('c')
If that’s now what you need, check out collect()
to collect only successful values and fail on any failed ones.
Public interface for _collect_all
method.
Cannot be modified directly.
Bases: AbstractFold
Concrete implementation of AbstractFold
of end users.
Use it by default.
Bases: object
Helper type for objects that should be immutable.
When applied, each instance becomes immutable. Nothing can be added or deleted from it.
>>> from returns.primitives.types import Immutable
>>> class MyModel(Immutable):
... ...
>>> model = MyModel()
>>> model.prop = 1
Traceback (most recent call last):
...
returns.primitives.exceptions.ImmutableStateError
See returns.primitives.container.BaseContainer
for examples.
Bases: Exception
Raised when a container can not be unwrapped into a meaningful value.
container (Unwrappable
) –
Bases: AttributeError
Raised when a container is forced to be mutated.
It is a sublclass of AttributeError
for two reasons:
It seems kinda reasonable to expect AttributeError
on attribute modification
It is used inside typing.py
this way,
we do have several typing features that requires that behaviour