Skip to content

Commit dc1c253

Browse files
committed
Closes #151, closes #146
1 parent a9aff3c commit dc1c253

19 files changed

+268
-145
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,14 @@ See (0Ver)[https://0ver.org/].
1212

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

1824
### Misc
1925

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -152,8 +152,8 @@ from returns.functions import box
152152
def fetch_user_profile(user_id: int) -> Result['UserProfile', Exception]:
153153
"""Fetches `UserProfile` TypedDict from foreign API."""
154154
return pipe(
155-
self._make_request,
156-
box(self._parse_json),
155+
_make_request,
156+
box(_parse_json),
157157
)(user_id)
158158

159159
@safe
@@ -229,10 +229,10 @@ from returns.functions import box
229229
def fetch_user_profile(user_id: int) -> Result['UserProfile', Exception]:
230230
"""Fetches `UserProfile` TypedDict from foreign API."""
231231
return pipe(
232-
self._make_request,
232+
_make_request,
233233
# after box: def (Result) -> Result
234234
# after IO.lift: def (IO[Result]) -> IO[Result]
235-
IO.lift(box(self._parse_json)),
235+
IO.lift(box(_parse_json)),
236236
)(user_id)
237237

238238
@impure

docs/pages/container.rst

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,9 @@ That's how they work:
351351
Take a note, that type changes.
352352
Also, take a note that ``Success(None)`` will be converted to ``Nothing``.
353353

354+
join
355+
~~~~
356+
354357
You can also use ``join`` to merge nested containers together:
355358

356359
.. code:: python
@@ -364,6 +367,32 @@ You can also use ``join`` to merge nested containers together:
364367
assert join(Maybe(Maybe(1))) == Maybe(1)
365368
assert join(Success(Success(1))) == Success(1)
366369
370+
coalesce
371+
~~~~~~~~
372+
373+
You can use :func:`returns.converters.coalesce_result`
374+
and :func:`returns.converters.coalesce_maybe` converters
375+
to covert containers to a regular value.
376+
377+
These functions accept two functions:
378+
one for successful case, one for failing case.
379+
380+
.. code:: python
381+
382+
from returns.converters import coalesce_result
383+
from returns.result import Success, Failure
384+
385+
def handle_success(state: int) -> float:
386+
return state / 2
387+
388+
def handle_failure(state: str) -> float:
389+
return 0.0
390+
391+
coalesce_result(handle_success, handle_failure)(Success(1))
392+
# => returns `0.5`
393+
coalesce_result(handle_success, handle_failure)(Failure(1))
394+
# => returns `0.0`
395+
367396
368397
API Reference
369398
-------------

returns/__init__.py

Lines changed: 0 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1 @@
11
# -*- coding: utf-8 -*-
2-
3-
"""
4-
We define public API here.
5-
6-
So, later our code can be used like so:
7-
8-
.. code:: python
9-
10-
import returns
11-
result: returns.Result[int, str]
12-
13-
See: https://github.com/dry-python/returns/issues/73
14-
"""
15-
16-
from returns.converters import maybe_to_result, result_to_maybe
17-
from returns.functions import compose, raise_exception
18-
from returns.io import IO, impure
19-
from returns.maybe import Maybe, Nothing, Some, maybe
20-
from returns.pipeline import is_successful, pipeline
21-
from returns.primitives.exceptions import UnwrapFailedError
22-
from returns.result import Failure, Result, Success, safe
23-
24-
__all__ = ( # noqa: WPS410
25-
# Functions:
26-
'compose',
27-
'raise_exception',
28-
29-
# IO:
30-
'IO',
31-
'impure',
32-
33-
# Maybe:
34-
'Some',
35-
'Nothing',
36-
'Maybe',
37-
'maybe',
38-
39-
# Result:
40-
'safe',
41-
'Failure',
42-
'Result',
43-
'Success',
44-
'UnwrapFailedError',
45-
46-
# pipeline:
47-
'is_successful',
48-
'pipeline',
49-
50-
# Converters:
51-
'result_to_maybe',
52-
'maybe_to_result',
53-
)

returns/converters.py

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
11
# -*- coding: utf-8 -*-
22

3-
from typing import TypeVar, overload
3+
from typing import Callable, TypeVar, overload
44

55
from returns.io import IO
66
from returns.maybe import Maybe
7+
from returns.pipeline import is_successful
78
from returns.result import Failure, Result, Success
89

10+
# Contianer internals:
911
_ValueType = TypeVar('_ValueType')
1012
_ErrorType = TypeVar('_ErrorType')
1113

14+
# Aliases:
15+
_FirstType = TypeVar('_FirstType')
16+
1217

1318
def result_to_maybe(
1419
result_container: Result[_ValueType, _ErrorType],
@@ -90,3 +95,63 @@ def join(container):
9095
9196
"""
9297
return container._inner_value # noqa: WPS437
98+
99+
100+
_coalesce_doc = """
101+
Accepts two functions that handle different cases of containers.
102+
103+
First one handles successful containers like ``Some`` and ``Success``,
104+
and second one for failed containers like ``Nothing`` and ``Failure``.
105+
106+
This function is useful when you need
107+
to coalesce two possible container states into one type.
108+
"""
109+
110+
111+
def _coalesce(success_handler, failure_handler):
112+
"""
113+
We need this function, because we cannot use a single typed function.
114+
115+
.. code:: python
116+
117+
>>> from returns.result import Success, Failure
118+
>>> f1 = lambda x: x + 1
119+
>>> f2 = lambda y: y + 'b'
120+
>>> coalesce_result(f1, f2)(Success(1)) == 2
121+
True
122+
>>> coalesce_result(f1, f2)(Failure('a')) == 'ab'
123+
True
124+
125+
>>> from returns.maybe import Some, Nothing
126+
>>> f1 = lambda x: x + 1
127+
>>> f2 = lambda _: 'a'
128+
>>> coalesce_maybe(f1, f2)(Some(1)) == 2
129+
True
130+
>>> coalesce_maybe(f1, f2)(Nothing) == 'a'
131+
True
132+
133+
"""
134+
def decorator(container):
135+
if is_successful(container):
136+
return success_handler(container.unwrap())
137+
return failure_handler(container.failure())
138+
return decorator
139+
140+
141+
coalesce_result: Callable[
142+
[
143+
Callable[[_ValueType], _FirstType],
144+
Callable[[_ErrorType], _FirstType],
145+
],
146+
Callable[[Result[_ValueType, _ErrorType]], _FirstType],
147+
] = _coalesce
148+
coalesce_result.__doc__ = _coalesce_doc
149+
150+
coalesce_maybe: Callable[
151+
[
152+
Callable[[_ValueType], _FirstType],
153+
Callable[[None], _FirstType],
154+
],
155+
Callable[[Maybe[_ValueType]], _FirstType],
156+
] = _coalesce
157+
coalesce_maybe.__doc__ = _coalesce_doc

returns/maybe.py

Lines changed: 38 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -67,24 +67,14 @@ def bind(
6767

6868
def fix(
6969
self,
70-
function: Union[
71-
# We use this union to make a good balance
72-
# between correct and useful typing:
73-
Callable[[None], Optional[_NewValueType]], # correct
74-
Callable[[], Optional[_NewValueType]], # useful
75-
],
70+
function: Callable[[None], Optional[_NewValueType]],
7671
) -> 'Maybe[_NewValueType]':
7772
"""Abstract method to compose container with a pure function."""
7873
raise NotImplementedError
7974

8075
def rescue(
8176
self,
82-
function: Union[
83-
# We use this union to make a good balance
84-
# between correct and useful typing:
85-
Callable[[None], 'Maybe[_NewValueType]'], # correct
86-
Callable[[], 'Maybe[_NewValueType]'], # useful
87-
],
77+
function: Callable[[None], 'Maybe[_NewValueType]'],
8878
) -> 'Maybe[_NewValueType]':
8979
"""Abstract method to compose container with other container."""
9080
raise NotImplementedError
@@ -100,6 +90,10 @@ def unwrap(self) -> _ValueType:
10090
"""Get value or raise exception."""
10191
raise NotImplementedError
10292

93+
def failure(self) -> None:
94+
"""Get failed value or raise exception."""
95+
raise NotImplementedError
96+
10397

10498
@final
10599
class _Nothing(Maybe[Any]):
@@ -160,17 +154,14 @@ def fix(self, function):
160154
161155
.. code:: python
162156
163-
>>> def fixable() -> str:
157+
>>> def fixable(_state) -> str:
164158
... return 'ab'
165159
...
166160
>>> Nothing.fix(fixable) == Some('ab')
167161
True
168162
169163
"""
170-
try:
171-
return Maybe.new(function())
172-
except TypeError:
173-
return Maybe.new(function(self._inner_value))
164+
return Maybe.new(function(self._inner_value))
174165

175166
def rescue(self, function):
176167
"""
@@ -181,17 +172,14 @@ def rescue(self, function):
181172
182173
.. code:: python
183174
184-
>>> def rescuable() -> Maybe[str]:
175+
>>> def rescuable(_state) -> Maybe[str]:
185176
... return Some('ab')
186177
...
187178
>>> Nothing.rescue(rescuable) == Some('ab')
188179
True
189180
190181
"""
191-
try:
192-
return function()
193-
except TypeError:
194-
return function(self._inner_value)
182+
return function(self._inner_value)
195183

196184
def value_or(self, default_value):
197185
"""
@@ -219,6 +207,18 @@ def unwrap(self):
219207
"""
220208
raise UnwrapFailedError(self)
221209

210+
def failure(self) -> None:
211+
"""
212+
Get failed value.
213+
214+
.. code:: python
215+
216+
>>> Nothing.failure() is None
217+
True
218+
219+
"""
220+
return self._inner_value
221+
222222

223223
@final
224224
class _Some(Maybe[_ValueType]):
@@ -278,7 +278,7 @@ def fix(self, function):
278278
279279
.. code:: python
280280
281-
>>> def fixable() -> str:
281+
>>> def fixable(_state) -> str:
282282
... return 'ab'
283283
...
284284
>>> Some('a').fix(fixable) == Some('a')
@@ -293,7 +293,7 @@ def rescue(self, function):
293293
294294
.. code:: python
295295
296-
>>> def rescuable() -> Maybe[str]:
296+
>>> def rescuable(_state) -> Maybe[str]:
297297
... return Some('ab')
298298
...
299299
>>> Some('a').rescue(rescuable) == Some('a')
@@ -326,6 +326,20 @@ def unwrap(self):
326326
"""
327327
return self._inner_value
328328

329+
def failure(self):
330+
"""
331+
Raises an exception, since it does not have a failure inside.
332+
333+
.. code:: python
334+
335+
>>> Some(1).failure()
336+
Traceback (most recent call last):
337+
...
338+
returns.primitives.exceptions.UnwrapFailedError
339+
340+
"""
341+
raise UnwrapFailedError(self)
342+
329343

330344
def Some(inner_value: Optional[_ValueType]) -> Maybe[_ValueType]: # noqa: N802
331345
"""Public unit function of protected `_Some` type."""

returns/primitives/container.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,13 @@ def unwrap(self) -> _ValueType:
154154
This method is the opposite of :meth:`~failure`.
155155
"""
156156

157+
def failure(self) -> _ErrorType:
158+
"""
159+
Custom magic method to unwrap inner value from the failed container.
160+
161+
This method is the opposite of :meth:`~unwrap`.
162+
"""
163+
157164

158165
@runtime
159166
class UnwrapableFailure(Protocol[_ValueType, _ErrorType]):
@@ -170,10 +177,3 @@ def alt(
170177
Works for containers that represent failure.
171178
Is the opposite of :meth:`~map`.
172179
"""
173-
174-
def failure(self) -> _ErrorType:
175-
"""
176-
Custom magic method to unwrap inner value from the failed container.
177-
178-
This method is the opposite of :meth:`~unwrap`.
179-
"""

0 commit comments

Comments
 (0)