from abc import ABCMeta
from functools import wraps
from typing import (
Any,
Callable,
ClassVar,
NoReturn,
Optional,
Type,
TypeVar,
Union,
)
from typing_extensions import final
from returns.interfaces.specific.maybe import MaybeBased2
from returns.primitives.container import BaseContainer, container_equality
from returns.primitives.exceptions import UnwrapFailedError
from returns.primitives.hkt import Kind1, SupportsKind1
# Definitions:
_ValueType = TypeVar('_ValueType', covariant=True)
_NewValueType = TypeVar('_NewValueType')
# Aliases:
_FirstType = TypeVar('_FirstType')
_SecondType = TypeVar('_SecondType')
[docs]class Maybe(
BaseContainer,
SupportsKind1['Maybe', _ValueType],
MaybeBased2[_ValueType, None],
metaclass=ABCMeta,
):
"""
Represents a result of a series of computations that can return ``None``.
An alternative to using exceptions or constant ``is None`` checks.
``Maybe`` is an abstract type and should not be instantiated directly.
Instead use ``Some`` and ``Nothing``.
See also:
- https://github.com/gcanti/fp-ts/blob/master/docs/modules/Option.ts.md
"""
_inner_value: Optional[_ValueType]
#: Alias for `Nothing`
empty: ClassVar['Maybe[Any]']
# These two are required for projects like `classes`:
#: Success type that is used to represent the successful computation.
success_type: ClassVar[Type['_Some']]
#: Failure type that is used to represent the failed computation.
failure_type: ClassVar[Type['_Nothing']]
#: Typesafe equality comparison with other `Result` objects.
equals = container_equality
[docs] def map( # noqa: WPS125
self,
function: Callable[[_ValueType], _NewValueType],
) -> 'Maybe[_NewValueType]':
"""
Composes successful container with a pure function.
.. code:: python
>>> from returns.maybe import Some, Nothing
>>> def mappable(string: str) -> str:
... return string + 'b'
>>> assert Some('a').map(mappable) == Some('ab')
>>> assert Nothing.map(mappable) == Nothing
"""
[docs] def apply(
self,
function: Kind1['Maybe', Callable[[_ValueType], _NewValueType]],
) -> 'Maybe[_NewValueType]':
"""
Calls a wrapped function in a container on this container.
.. code:: python
>>> from returns.maybe import Some, Nothing
>>> def appliable(string: str) -> str:
... return string + 'b'
>>> assert Some('a').apply(Some(appliable)) == Some('ab')
>>> assert Some('a').apply(Nothing) == Nothing
>>> assert Nothing.apply(Some(appliable)) == Nothing
>>> assert Nothing.apply(Nothing) == Nothing
"""
[docs] def bind(
self,
function: Callable[[_ValueType], Kind1['Maybe', _NewValueType]],
) -> 'Maybe[_NewValueType]':
"""
Composes successful container with a function that returns a container.
.. code:: python
>>> from returns.maybe import Nothing, Maybe, Some
>>> def bindable(string: str) -> Maybe[str]:
... return Some(string + 'b')
>>> assert Some('a').bind(bindable) == Some('ab')
>>> assert Nothing.bind(bindable) == Nothing
"""
[docs] def bind_optional(
self,
function: Callable[[_ValueType], Optional[_NewValueType]],
) -> 'Maybe[_NewValueType]':
"""
Binds a function returning an optional value over a container.
.. code:: python
>>> from returns.maybe import Some, Nothing
>>> from typing import Optional
>>> def bindable(arg: str) -> Optional[int]:
... return len(arg) if arg else None
>>> assert Some('a').bind_optional(bindable) == Some(1)
>>> assert Some('').bind_optional(bindable) == Nothing
"""
[docs] def lash(
self,
function: Callable[[Any], Kind1['Maybe', _ValueType]],
) -> 'Maybe[_ValueType]':
"""
Composes failed container with a function that returns a container.
.. code:: python
>>> from returns.maybe import Maybe, Some, Nothing
>>> def lashable(arg=None) -> Maybe[str]:
... return Some('b')
>>> assert Some('a').lash(lashable) == Some('a')
>>> assert Nothing.lash(lashable) == Some('b')
We need this feature to make ``Maybe`` compatible
with different ``Result`` like oeprations.
"""
[docs] def value_or(
self,
default_value: _NewValueType,
) -> Union[_ValueType, _NewValueType]:
"""
Get value from successful container or default value from failed one.
.. code:: python
>>> from returns.maybe import Nothing, Some
>>> assert Some(0).value_or(1) == 0
>>> assert Nothing.value_or(1) == 1
"""
[docs] def or_else_call(
self,
function: Callable[[], _NewValueType],
) -> Union[_ValueType, _NewValueType]:
"""
Get value from successful container or default value from failed one.
Really close to :meth:`~Maybe.value_or` but works with lazy values.
This method is unique to ``Maybe`` container, because other containers
do have ``.alt`` method.
But, ``Maybe`` does not have this method.
There's nothing to ``alt`` in ``Nothing``.
Instead, it has this method to execute
some function if called on a failed container:
.. code:: pycon
>>> from returns.maybe import Some, Nothing
>>> assert Some(1).or_else_call(lambda: 2) == 1
>>> assert Nothing.or_else_call(lambda: 2) == 2
It might be useful to work with exceptions as well:
.. code:: pycon
>>> def fallback() -> NoReturn:
... raise ValueError('Nothing!')
>>> Nothing.or_else_call(fallback)
Traceback (most recent call last):
...
ValueError: Nothing!
"""
[docs] def unwrap(self) -> _ValueType:
"""
Get value from successful container or raise exception for failed one.
.. code:: pycon
:force:
>>> from returns.maybe import Nothing, Some
>>> assert Some(1).unwrap() == 1
>>> Nothing.unwrap()
Traceback (most recent call last):
...
returns.primitives.exceptions.UnwrapFailedError
""" # noqa: RST399
[docs] def failure(self) -> None:
"""
Get failed value from failed container or raise exception from success.
.. code:: pycon
:force:
>>> from returns.maybe import Nothing, Some
>>> assert Nothing.failure() is None
>>> Some(1).failure()
Traceback (most recent call last):
...
returns.primitives.exceptions.UnwrapFailedError
""" # noqa: RST399
[docs] @classmethod
def from_value(
cls, inner_value: _NewValueType,
) -> 'Maybe[_NewValueType]':
"""
Creates new instance of ``Maybe`` container based on a value.
.. code:: python
>>> from returns.maybe import Maybe, Some
>>> assert Maybe.from_value(1) == Some(1)
>>> assert Maybe.from_value(None) == Some(None)
"""
return _Some(inner_value)
[docs] @classmethod
def from_optional(
cls, inner_value: Optional[_NewValueType],
) -> 'Maybe[_NewValueType]':
"""
Creates new instance of ``Maybe`` container based on an optional value.
.. code:: python
>>> from returns.maybe import Maybe, Some, Nothing
>>> assert Maybe.from_optional(1) == Some(1)
>>> assert Maybe.from_optional(None) == Nothing
"""
if inner_value is None:
return _Nothing(inner_value)
return _Some(inner_value)
@final
class _Nothing(Maybe[Any]):
"""Represents an empty state."""
_inner_value: None
def __init__(self, inner_value: None = None) -> None: # noqa: WPS632
"""
Private constructor for ``_Nothing`` type.
Use :attr:`~Nothing` instead.
Wraps the given value in the ``_Nothing`` container.
``inner_value`` can only be ``None``.
"""
super().__init__(None)
def __repr__(self):
"""
Custom ``str`` definition without the state inside.
.. code:: python
>>> from returns.maybe import Nothing
>>> assert str(Nothing) == '<Nothing>'
>>> assert repr(Nothing) == '<Nothing>'
"""
return '<Nothing>'
def map(self, function): # noqa: WPS125
"""Does nothing for ``Nothing``."""
return self
def apply(self, container):
"""Does nothing for ``Nothing``."""
return self
def bind(self, function):
"""Does nothing for ``Nothing``."""
return self
def bind_optional(self, function):
"""Does nothing."""
return self
def lash(self, function):
"""Composes this container with a function returning container."""
return function(None)
def value_or(self, default_value):
"""Returns default value."""
return default_value
def or_else_call(self, function):
"""Returns the result of a passed function."""
return function()
def unwrap(self):
"""Raises an exception, since it does not have a value inside."""
raise UnwrapFailedError(self)
def failure(self) -> None:
"""Returns failed value."""
return self._inner_value
@final
class _Some(Maybe[_ValueType]):
"""
Represents a calculation which has succeeded and contains the value.
Quite similar to ``Success`` type.
"""
_inner_value: _ValueType
def __init__(self, inner_value: _ValueType) -> None:
"""
Private type constructor.
Please, use :func:`~Some` instead.
Required for typing.
"""
super().__init__(inner_value)
def map(self, function): # noqa: WPS125
"""Composes current container with a pure function."""
return _Some(function(self._inner_value))
def apply(self, container):
"""Calls a wrapped function in a container on this container."""
if isinstance(container, self.success_type):
return self.map(container.unwrap()) # type: ignore
return container
def bind(self, function):
"""Binds current container to a function that returns container."""
return function(self._inner_value)
def bind_optional(self, function):
"""Binds a function returning an optional value over a container."""
return Maybe.from_optional(function(self._inner_value))
def lash(self, function):
"""Does nothing for ``Some``."""
return self
def value_or(self, default_value):
"""Returns inner value for successful container."""
return self._inner_value
def or_else_call(self, function):
"""Returns inner value for successful container."""
return self._inner_value
def unwrap(self):
"""Returns inner value for successful container."""
return self._inner_value
def failure(self):
"""Raises exception for successful container."""
raise UnwrapFailedError(self)
Maybe.success_type = _Some
Maybe.failure_type = _Nothing
[docs]def Some(inner_value: _NewValueType) -> Maybe[_NewValueType]: # noqa: N802
"""
Public unit function of protected :class:`~_Some` type.
Can return ``Some(None)`` for passed ``None`` argument.
Because ``Some(None)`` does make sense.
.. code:: python
>>> from returns.maybe import Some
>>> assert str(Some(1)) == '<Some: 1>'
>>> assert str(Some(None)) == '<Some: None>'
"""
return _Some(inner_value)
#: Public unit value of protected :class:`~_Nothing` type.
Nothing: Maybe[NoReturn] = _Nothing()
Maybe.empty = Nothing
[docs]def maybe(
function: Callable[..., Optional[_ValueType]],
) -> Callable[..., Maybe[_ValueType]]:
"""
Decorator to convert ``None``-returning function to ``Maybe`` container.
This decorator works with sync functions only. Example:
.. code:: python
>>> from typing import Optional
>>> from returns.maybe import Nothing, Some, maybe
>>> @maybe
... def might_be_none(arg: int) -> Optional[int]:
... if arg == 0:
... return None
... return 1 / arg
>>> assert might_be_none(0) == Nothing
>>> assert might_be_none(1) == Some(1.0)
Requires our :ref:`mypy plugin <mypy-plugins>`.
"""
@wraps(function)
def decorator(*args, **kwargs):
return Maybe.from_optional(function(*args, **kwargs))
return decorator