Source code for returns.io
# -*- coding: utf-8 -*-
from functools import wraps
from inspect import iscoroutinefunction
from typing import Callable, Coroutine, Generic, TypeVar, overload
from typing_extensions import final
from returns._generated.squash import _squash as io_squash # noqa: F401, WPS436
from returns.primitives.container import BaseContainer
_ValueType = TypeVar('_ValueType', covariant=True)
_NewValueType = TypeVar('_NewValueType')
# Helpers:
_FirstType = TypeVar('_FirstType')
_SecondType = TypeVar('_SecondType')
[docs]@final
class IO(BaseContainer, Generic[_ValueType]):
"""
Explicit marker for impure function results.
We call it "marker" since once it is marked, it cannot be unmarked.
``IO`` is also a container.
But, it is different in a way
that it cannot be unwrapped / rescued / fixed.
There's no way to directly get its internal value.
"""
_inner_value: _ValueType
def __init__(self, inner_value: _ValueType) -> None:
"""Required for typing."""
super().__init__(inner_value)
[docs] def map( # noqa: A003
self,
function: Callable[[_ValueType], _NewValueType],
) -> 'IO[_NewValueType]':
"""
Applies function to the inner value.
Applies 'function' to the contents of the IO instance
and returns a new IO object containing the result.
'function' should accept a single "normal" (non-container) argument
and return a non-container result.
.. code:: python
>>> def mappable(string: str) -> str:
... return string + 'b'
...
>>> IO('a').map(mappable) == IO('ab')
True
"""
return IO(function(self._inner_value))
[docs] def bind(
self, function: Callable[[_ValueType], 'IO[_NewValueType]'],
) -> 'IO[_NewValueType]':
"""
Applies 'function' to the result of a previous calculation.
'function' should accept a single "normal" (non-container) argument
and return IO type object.
.. code:: python
>>> def bindable(string: str) -> IO[str]:
... return IO(string + 'b')
...
>>> IO('a').bind(bindable) == IO('ab')
True
"""
return function(self._inner_value)
[docs] @classmethod
def lift(
cls,
function: Callable[[_ValueType], _NewValueType],
) -> Callable[['IO[_ValueType]'], 'IO[_NewValueType]']:
"""
Lifts function to be wrapped in ``IO`` for better composition.
In other words, it modifies the function's
signature from: ``a -> b`` to: ``IO[a] -> IO[b]``
This is how it should be used:
.. code:: python
>>> def example(argument: int) -> float:
... return argument / 2 # not exactly IO action!
...
>>> IO.lift(example)(IO(2)) == IO(1.0)
True
See also:
- https://wiki.haskell.org/Lifting
- https://github.com/witchcrafters/witchcraft
- https://en.wikipedia.org/wiki/Natural_transformation
"""
return lambda container: cls.map(container, function)
@overload
def impure( # type: ignore
function: Callable[..., Coroutine[_FirstType, _SecondType, _NewValueType]],
) -> Callable[
...,
Coroutine[_FirstType, _SecondType, IO[_NewValueType]],
]:
"""Case for async functions."""
@overload
def impure(
function: Callable[..., _NewValueType],
) -> Callable[..., IO[_NewValueType]]:
"""Case for regular functions."""
[docs]def impure(function):
"""
Decorator to mark function that it returns :py:class:`IO` container.
Supports both async and regular functions.
"""
if iscoroutinefunction(function):
async def decorator(*args, **kwargs): # noqa: WPS430
return IO(await function(*args, **kwargs))
else:
def decorator(*args, **kwargs): # noqa: WPS430
return IO(function(*args, **kwargs))
return wraps(function)(decorator)