We feature several helper functions to make your developer experience better.
We also ship an utility function to compose two different functions together.
from returns.functions import compose
bool_after_int = compose(int, bool)
bool_after_int('1') # => True
bool_after_int('0') # => False
Composition is also type-safe. The only limitation is that we only support functions with one argument and one return to be composed.
Only works with regular functions (not async).
We also ship returns.functions.identity()
function
to help you with the composition.
Identity function is a simple concept: it just returns its argument. If you wonder why do we need this function, please read below:
Without box()
it would be very hard to declaratively compose two entities:
Existings container
Existing functions that accepts a regular value and returns a container
We can compose these entities with .bind
when calling it directly,
but how can we do it inversevely?
from returns.functions import box
from returns.maybe import Maybe
def bindable(arg: int) -> Maybe[str]:
...
container: Maybe[int]
# We now have two way of composining these entities.
# 1. Via ``.bind``:
container.bind(bindable) # works!
# 2. Or via ``box``, the same but in the inverse way:
box(bindable)(container)
That’s it.
We need tap()
function to easily compose values
with functions that does not return.
For example you sometimes need to print()
values inside your pipe:
from returns.functions import tap
result = tap(print)(1) # will print and return 1
# => prints 1
assert result == 1
# => True
You can also use untap
function to turn any function
return type to None
.
This is also sometimes helpful for a typed function composition.
Sometimes you really want to reraise an exception from Failure[Exception]
due to some existing API (or a dirty hack).
We allow you to do that with ease!
from returns.functions import raise_exception
@pipeline(Result)
def create_account_and_user(username: str) -> ...:
"""
Creates new Account-User pair.
Imagine, that you need to reraise ValidationErrors due to existing API.
"""
return _validate_user(
username,
).alt(
# What happens here is interesting, since you do not let your
# unwrap to fail with UnwrapFailedError, but instead
# allows you to reraise a wrapped exception.
# In this case `ValidationError()` will be thrown
# before `UnwrapFailedError`
raise_exception,
)
def _validate_user(username: str) -> Result['User', ValidationError]:
...
Use this with caution. We try to remove exceptions from our code base. Original proposal is here.
identity
(instance)[source]¶Function that returns its argument.
>>> identity(1)
1
>>> identity([1, 2, 3])
[1, 2, 3]
Why do we even need this? Identity functions help us with the composition.
Imagine, that you want to use returns.converters.coalesce_result()
like so:
from returns.result import Result
from returns.converters import coalesce_result
numbers: Result[int, float]
# Now you want to fold `number` into `int` type:
number: int = coalesce_result(identity, int)(numbers)
# Done!
See also
~_FirstType
compose
(first, second)[source]¶Allows function composition.
Works as: second . first
or first() |> second()
.
You can read it as “second after first”.
>>> compose(float, int)('123.5')
123
We can only compose functions with one argument and one return. Type checked.
Callable
[[~_FirstType], ~_ThirdType]
tap
(function)[source]¶Allows to apply some function and return an argument, instead of a result.
Is usefull for composing functions with
side-effects like print()
, logger.log()
, etc.
>>> tap(print)(1)
1
1
>>> tap(lambda _: 1)(2)
2
Callable
[[~_FirstType], ~_FirstType]
untap
(function)[source]¶Allows to apply some function and always return None
as a result.
Is usefull for composing functions that do some side effects and return some nosense.
Is the kind of a reverse of the tap
function.
>>> def strange_log(arg: int) -> int:
... print(arg)
... return arg
>>> untap(strange_log)(2)
2
>>> untap(tap(lambda _: 1))(2)
Callable
[[~_FirstType], None
]
raise_exception
(exception)[source]¶Helper function to raise exceptions as a function.
It might be required as a compatibility tool for existing APIs. That’s how it can be used:
>>> from returns.result import Failure, Result
>>> # Some operation result:
>>> user: Result[int, ValueError] = Failure(ValueError('boom'))
>>> # Here we unwrap internal exception and raise it:
>>> user.fix(raise_exception)
Traceback (most recent call last):
...
ValueError: boom
NoReturn
box
(function)¶Boxes function’s input parameter from a regular value to a container.
In other words, it modifies the function
signature from: a -> Container[b]
to: Container[a] -> Container[b]
This is how it should be used:
>>> from returns.functions import box
>>> from returns.maybe import Maybe, Some
>>> def example(argument: int) -> Maybe[int]:
... return Some(argument + 1)
...
>>> box(example)(Some(1)) == Some(2)
True