from abc import abstractmethod
from typing import (
Callable,
ClassVar,
NoReturn,
Optional,
Sequence,
Type,
TypeVar,
Union,
final,
)
from returns.interfaces import equable, failable, unwrappable
from returns.primitives.asserts import assert_equal
from returns.primitives.hkt import KindN
from returns.primitives.laws import (
Law,
Law2,
Law3,
Lawful,
LawSpecDef,
law_definition,
)
_FirstType = TypeVar('_FirstType')
_SecondType = TypeVar('_SecondType')
_ThirdType = TypeVar('_ThirdType')
_UpdatedType = TypeVar('_UpdatedType')
_MaybeLikeType = TypeVar('_MaybeLikeType', bound='MaybeLikeN')
# New values:
_ValueType = TypeVar('_ValueType')
# Only used in laws:
_NewType1 = TypeVar('_NewType1')
[docs]@final
class _LawSpec(LawSpecDef):
"""
Maybe laws.
We need to be sure that
``.map``, ``.bind``, ``.bind_optional``, and ``.lash``
works correctly for both successful and failed types.
"""
__slots__ = ()
[docs] @law_definition
def map_short_circuit_law(
container: 'MaybeLikeN[_FirstType, _SecondType, _ThirdType]',
function: Callable[[_FirstType], _NewType1],
) -> None:
"""Ensures that you cannot map from failures."""
assert_equal(
container.from_optional(None).map(function),
container.from_optional(None),
)
[docs] @law_definition
def bind_short_circuit_law(
container: 'MaybeLikeN[_FirstType, _SecondType, _ThirdType]',
function: Callable[
[_FirstType],
KindN['MaybeLikeN', _NewType1, _SecondType, _ThirdType],
],
) -> None:
"""Ensures that you cannot bind from failures."""
assert_equal(
container.from_optional(None).bind(function),
container.from_optional(None),
)
[docs] @law_definition
def bind_optional_short_circuit_law(
container: 'MaybeLikeN[_FirstType, _SecondType, _ThirdType]',
function: Callable[[_FirstType], Optional[_NewType1]],
) -> None:
"""Ensures that you cannot bind from failures."""
assert_equal(
container.from_optional(None).bind_optional(function),
container.from_optional(None),
)
[docs] @law_definition
def lash_short_circuit_law(
raw_value: _FirstType,
container: 'MaybeLikeN[_FirstType, _SecondType, _ThirdType]',
function: Callable[
[_SecondType],
KindN['MaybeLikeN', _FirstType, _NewType1, _ThirdType],
],
) -> None:
"""Ensures that you cannot lash a success."""
assert_equal(
container.from_value(raw_value).lash(function),
container.from_value(raw_value),
)
[docs] @law_definition
def unit_structure_law(
container: 'MaybeLikeN[_FirstType, _SecondType, _ThirdType]',
function: Callable[[_FirstType], None],
) -> None:
"""Ensures ``None`` is treated specially."""
assert_equal(
container.bind_optional(function),
container.from_optional(None),
)
[docs]class MaybeLikeN(
failable.SingleFailableN[_FirstType, _SecondType, _ThirdType],
Lawful['MaybeLikeN[_FirstType, _SecondType, _ThirdType]'],
):
"""
Type for values that do look like a ``Maybe``.
For example, ``RequiresContextMaybe`` should be created from this interface.
Cannot be unwrapped or compared.
"""
__slots__ = ()
_laws: ClassVar[Sequence[Law]] = (
Law2(_LawSpec.map_short_circuit_law),
Law2(_LawSpec.bind_short_circuit_law),
Law2(_LawSpec.bind_optional_short_circuit_law),
Law3(_LawSpec.lash_short_circuit_law),
Law2(_LawSpec.unit_structure_law),
)
[docs] @abstractmethod
def bind_optional(
self: _MaybeLikeType,
function: Callable[[_FirstType], Optional[_UpdatedType]],
) -> KindN[_MaybeLikeType, _UpdatedType, _SecondType, _ThirdType]:
"""Binds a function that returns ``Optional`` values."""
[docs] @classmethod
@abstractmethod
def from_optional(
cls: Type[_MaybeLikeType], # noqa: N805
inner_value: Optional[_ValueType],
) -> KindN[_MaybeLikeType, _ValueType, _SecondType, _ThirdType]:
"""Unit method to create containers from ``Optional`` value."""
#: Type alias for kinds with two type arguments.
MaybeLike2 = MaybeLikeN[_FirstType, _SecondType, NoReturn]
#: Type alias for kinds with three type arguments.
MaybeLike3 = MaybeLikeN[_FirstType, _SecondType, _ThirdType]
[docs]class MaybeBasedN(
MaybeLikeN[_FirstType, _SecondType, _ThirdType],
unwrappable.Unwrappable[_FirstType, None],
equable.Equable,
):
"""
Concrete interface for ``Maybe`` type.
Can be unwrapped and compared.
"""
__slots__ = ()
[docs] @abstractmethod
def or_else_call(
self,
function: Callable[[], _ValueType],
) -> Union[_FirstType, _ValueType]:
"""Calls a function in case there nothing to unwrap."""
#: Type alias for kinds with two type arguments.
MaybeBased2 = MaybeBasedN[_FirstType, _SecondType, NoReturn]
#: Type alias for kinds with three type arguments.
MaybeBased3 = MaybeBasedN[_FirstType, _SecondType, _ThirdType]