Skip to content

Commit

Permalink
Closes #151, closes #146
Browse files Browse the repository at this point in the history
  • Loading branch information
sobolevn committed Aug 29, 2019
1 parent a9aff3c commit dc1c253
Show file tree
Hide file tree
Showing 19 changed files with 268 additions and 145 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,14 @@ See (0Ver)[https://0ver.org/].

- **Breaking**: now `pipe()` does not require argument to be the first value,
instead it is required to use: `pipe(f1, f2, f3, f4)(value)`
- **Breaking**: dropped everything from `returns/__init__.py`,
because we now have quite a lot of stuff
- **Breaking**: dropped support of zero argument functions for `Nothing.fix`
- **Breaking**: dropped support of zero argument functions for `Nothing.rescue`
- `Maybe` now has `.failure()` to match the same API as `Result`
- Adds `tap` function
- Now `pipe` allows to pipe 8 steps
- Adds `coalesce_conatiner` coverter

### Misc

Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,8 @@ from returns.functions import box
def fetch_user_profile(user_id: int) -> Result['UserProfile', Exception]:
"""Fetches `UserProfile` TypedDict from foreign API."""
return pipe(
self._make_request,
box(self._parse_json),
_make_request,
box(_parse_json),
)(user_id)

@safe
Expand Down Expand Up @@ -229,10 +229,10 @@ from returns.functions import box
def fetch_user_profile(user_id: int) -> Result['UserProfile', Exception]:
"""Fetches `UserProfile` TypedDict from foreign API."""
return pipe(
self._make_request,
_make_request,
# after box: def (Result) -> Result
# after IO.lift: def (IO[Result]) -> IO[Result]
IO.lift(box(self._parse_json)),
IO.lift(box(_parse_json)),
)(user_id)

@impure
Expand Down
29 changes: 29 additions & 0 deletions docs/pages/container.rst
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,9 @@ That's how they work:
Take a note, that type changes.
Also, take a note that ``Success(None)`` will be converted to ``Nothing``.

join
~~~~

You can also use ``join`` to merge nested containers together:

.. code:: python
Expand All @@ -364,6 +367,32 @@ You can also use ``join`` to merge nested containers together:
assert join(Maybe(Maybe(1))) == Maybe(1)
assert join(Success(Success(1))) == Success(1)
coalesce
~~~~~~~~

You can use :func:`returns.converters.coalesce_result`
and :func:`returns.converters.coalesce_maybe` converters
to covert containers to a regular value.

These functions accept two functions:
one for successful case, one for failing case.

.. code:: python
from returns.converters import coalesce_result
from returns.result import Success, Failure
def handle_success(state: int) -> float:
return state / 2
def handle_failure(state: str) -> float:
return 0.0
coalesce_result(handle_success, handle_failure)(Success(1))
# => returns `0.5`
coalesce_result(handle_success, handle_failure)(Failure(1))
# => returns `0.0`
API Reference
-------------
Expand Down
52 changes: 0 additions & 52 deletions returns/__init__.py
Original file line number Diff line number Diff line change
@@ -1,53 +1 @@
# -*- coding: utf-8 -*-

"""
We define public API here.
So, later our code can be used like so:
.. code:: python
import returns
result: returns.Result[int, str]
See: https://github.com/dry-python/returns/issues/73
"""

from returns.converters import maybe_to_result, result_to_maybe
from returns.functions import compose, raise_exception
from returns.io import IO, impure
from returns.maybe import Maybe, Nothing, Some, maybe
from returns.pipeline import is_successful, pipeline
from returns.primitives.exceptions import UnwrapFailedError
from returns.result import Failure, Result, Success, safe

__all__ = ( # noqa: WPS410
# Functions:
'compose',
'raise_exception',

# IO:
'IO',
'impure',

# Maybe:
'Some',
'Nothing',
'Maybe',
'maybe',

# Result:
'safe',
'Failure',
'Result',
'Success',
'UnwrapFailedError',

# pipeline:
'is_successful',
'pipeline',

# Converters:
'result_to_maybe',
'maybe_to_result',
)
67 changes: 66 additions & 1 deletion returns/converters.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
# -*- coding: utf-8 -*-

from typing import TypeVar, overload
from typing import Callable, TypeVar, overload

from returns.io import IO
from returns.maybe import Maybe
from returns.pipeline import is_successful
from returns.result import Failure, Result, Success

# Contianer internals:
_ValueType = TypeVar('_ValueType')
_ErrorType = TypeVar('_ErrorType')

# Aliases:
_FirstType = TypeVar('_FirstType')


def result_to_maybe(
result_container: Result[_ValueType, _ErrorType],
Expand Down Expand Up @@ -90,3 +95,63 @@ def join(container):
"""
return container._inner_value # noqa: WPS437


_coalesce_doc = """
Accepts two functions that handle different cases of containers.
First one handles successful containers like ``Some`` and ``Success``,
and second one for failed containers like ``Nothing`` and ``Failure``.
This function is useful when you need
to coalesce two possible container states into one type.
"""


def _coalesce(success_handler, failure_handler):
"""
We need this function, because we cannot use a single typed function.
.. code:: python
>>> from returns.result import Success, Failure
>>> f1 = lambda x: x + 1
>>> f2 = lambda y: y + 'b'
>>> coalesce_result(f1, f2)(Success(1)) == 2
True
>>> coalesce_result(f1, f2)(Failure('a')) == 'ab'
True
>>> from returns.maybe import Some, Nothing
>>> f1 = lambda x: x + 1
>>> f2 = lambda _: 'a'
>>> coalesce_maybe(f1, f2)(Some(1)) == 2
True
>>> coalesce_maybe(f1, f2)(Nothing) == 'a'
True
"""
def decorator(container):
if is_successful(container):
return success_handler(container.unwrap())
return failure_handler(container.failure())
return decorator


coalesce_result: Callable[
[
Callable[[_ValueType], _FirstType],
Callable[[_ErrorType], _FirstType],
],
Callable[[Result[_ValueType, _ErrorType]], _FirstType],
] = _coalesce
coalesce_result.__doc__ = _coalesce_doc

coalesce_maybe: Callable[
[
Callable[[_ValueType], _FirstType],
Callable[[None], _FirstType],
],
Callable[[Maybe[_ValueType]], _FirstType],
] = _coalesce
coalesce_maybe.__doc__ = _coalesce_doc
62 changes: 38 additions & 24 deletions returns/maybe.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,24 +67,14 @@ def bind(

def fix(
self,
function: Union[
# We use this union to make a good balance
# between correct and useful typing:
Callable[[None], Optional[_NewValueType]], # correct
Callable[[], Optional[_NewValueType]], # useful
],
function: Callable[[None], Optional[_NewValueType]],
) -> 'Maybe[_NewValueType]':
"""Abstract method to compose container with a pure function."""
raise NotImplementedError

def rescue(
self,
function: Union[
# We use this union to make a good balance
# between correct and useful typing:
Callable[[None], 'Maybe[_NewValueType]'], # correct
Callable[[], 'Maybe[_NewValueType]'], # useful
],
function: Callable[[None], 'Maybe[_NewValueType]'],
) -> 'Maybe[_NewValueType]':
"""Abstract method to compose container with other container."""
raise NotImplementedError
Expand All @@ -100,6 +90,10 @@ def unwrap(self) -> _ValueType:
"""Get value or raise exception."""
raise NotImplementedError

def failure(self) -> None:
"""Get failed value or raise exception."""
raise NotImplementedError


@final
class _Nothing(Maybe[Any]):
Expand Down Expand Up @@ -160,17 +154,14 @@ def fix(self, function):
.. code:: python
>>> def fixable() -> str:
>>> def fixable(_state) -> str:
... return 'ab'
...
>>> Nothing.fix(fixable) == Some('ab')
True
"""
try:
return Maybe.new(function())
except TypeError:
return Maybe.new(function(self._inner_value))
return Maybe.new(function(self._inner_value))

def rescue(self, function):
"""
Expand All @@ -181,17 +172,14 @@ def rescue(self, function):
.. code:: python
>>> def rescuable() -> Maybe[str]:
>>> def rescuable(_state) -> Maybe[str]:
... return Some('ab')
...
>>> Nothing.rescue(rescuable) == Some('ab')
True
"""
try:
return function()
except TypeError:
return function(self._inner_value)
return function(self._inner_value)

def value_or(self, default_value):
"""
Expand Down Expand Up @@ -219,6 +207,18 @@ def unwrap(self):
"""
raise UnwrapFailedError(self)

def failure(self) -> None:
"""
Get failed value.
.. code:: python
>>> Nothing.failure() is None
True
"""
return self._inner_value


@final
class _Some(Maybe[_ValueType]):
Expand Down Expand Up @@ -278,7 +278,7 @@ def fix(self, function):
.. code:: python
>>> def fixable() -> str:
>>> def fixable(_state) -> str:
... return 'ab'
...
>>> Some('a').fix(fixable) == Some('a')
Expand All @@ -293,7 +293,7 @@ def rescue(self, function):
.. code:: python
>>> def rescuable() -> Maybe[str]:
>>> def rescuable(_state) -> Maybe[str]:
... return Some('ab')
...
>>> Some('a').rescue(rescuable) == Some('a')
Expand Down Expand Up @@ -326,6 +326,20 @@ def unwrap(self):
"""
return self._inner_value

def failure(self):
"""
Raises an exception, since it does not have a failure inside.
.. code:: python
>>> Some(1).failure()
Traceback (most recent call last):
...
returns.primitives.exceptions.UnwrapFailedError
"""
raise UnwrapFailedError(self)


def Some(inner_value: Optional[_ValueType]) -> Maybe[_ValueType]: # noqa: N802
"""Public unit function of protected `_Some` type."""
Expand Down
14 changes: 7 additions & 7 deletions returns/primitives/container.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,13 @@ def unwrap(self) -> _ValueType:
This method is the opposite of :meth:`~failure`.
"""

def failure(self) -> _ErrorType:
"""
Custom magic method to unwrap inner value from the failed container.
This method is the opposite of :meth:`~unwrap`.
"""


@runtime
class UnwrapableFailure(Protocol[_ValueType, _ErrorType]):
Expand All @@ -170,10 +177,3 @@ def alt(
Works for containers that represent failure.
Is the opposite of :meth:`~map`.
"""

def failure(self) -> _ErrorType:
"""
Custom magic method to unwrap inner value from the failed container.
This method is the opposite of :meth:`~unwrap`.
"""
Loading

0 comments on commit dc1c253

Please sign in to comment.