Skip to content

Commit 26636d7

Browse files
committed
Adds converters, closes #92
1 parent 0b4b8cb commit 26636d7

File tree

10 files changed

+170
-4
lines changed

10 files changed

+170
-4
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ We follow Semantic Versions since the `0.1.0` release.
88
### Features
99

1010
- Reintroduces the `Maybe` monad, typed!
11+
- Introduces converters from one type to another
1112
- Adds `mypy` plugin to type decorators
1213
- Complete rewrite of `Result` types
1314
- Partial API change, now `Success` and `Failure` are not types, but functions

docs/pages/container.rst

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,10 +293,46 @@ Here's the full table of compositions that make sense:
293293

294294
- ``IO[Result[A, B]]`` ✅
295295
- ``Result[IO[A], B]`` ✅
296+
- ``IO[Maybe[A]]`` ✅
297+
- ``Maybe[IO[A]]`` ✅
296298
- ``IO[IO[A]]`` 🚫
297299
- ``Result[A, IO[A]]`` 🚫
300+
- ``Result[Maybe[A], B]`` 🚫
301+
- ``Result[A, Maybe[B]]`` 🚫
298302
- ``Result[Result[A, B], C]`` 🚫
299303
- ``Result[A, Result[B, C]]`` 🚫
304+
- ``Maybe[Result[A, B]]`` 🚫
305+
306+
You can use :ref:`converters` to convert ``Maybe`` and ``Result`` containers.
307+
So, you don't have to compose them.
308+
309+
310+
.. converters_:
311+
312+
Converters
313+
----------
314+
315+
We have several helper functions
316+
to convert containers from ``Maybe`` to ``Result`` and back again:
317+
318+
- ``maybe_to_result`` that converts ``Maybe`` to ``Result``
319+
- ``result_to_maybe`` that converts ``Result`` to ``Maybe``
320+
321+
That's how they work:
322+
323+
.. code:: python
324+
325+
from returns.converters import maybe_to_result, result_to_maybe
326+
from returns.maybe import Maybe
327+
from returns.result import Result
328+
329+
result: Result[int, Exception]
330+
maybe: Maybe[int] = result_to_maybe(result)
331+
new_result: Result[int, None] = maybe_to_result(maybe)
332+
333+
Take a note, that type changes.
334+
Also, take a note that ``Success(None)`` will be converted to ``Nothing``.
335+
300336

301337
API Reference
302338
-------------

docs/pages/io.rst

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,6 @@ since ``IO`` operation is safe and cannot throw.
220220
.. code:: python
221221
222222
import requests
223-
224223
from returns.io import IO, impure
225224
from returns.result import Result, safe
226225
@@ -233,6 +232,39 @@ since ``IO`` operation is safe and cannot throw.
233232
In this case the whole result is marked as impure.
234233
Since its failures are impure as well.
235234

235+
What is the difference between IO[Maybe[A]] and Maybe[IO[A]]?
236+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
237+
238+
The similar question is about ``IO`` and ``Maybe`` composition.
239+
Let's illustrate it with the code example:
240+
241+
.. code:: python
242+
243+
from returns.maybe import Maybe, Nothing
244+
from returns.io import IO
245+
246+
def maybe_ask_user(should_ask: bool) -> Maybe[IO[str]]:
247+
if should_ask:
248+
return Maybe.new(IO(input('Asking!')))
249+
return Nothing
250+
251+
In this example ``IO`` might not happen at all.
252+
253+
.. code:: python
254+
255+
from returns.maybe import Maybe, Nothing
256+
from returns.io import IO
257+
258+
def ask_user() -> IO[Maybe[str]]:
259+
prompt = input('Asking!')
260+
if prompt:
261+
return Maybe.new(IO(prompt))
262+
return IO(Nothing)
263+
264+
In this second case, we always do ``IO``, but we return ``Nothing``
265+
if user inputs an empty string
266+
(because we need this business logic for some reason).
267+
236268
Why can't we unwrap values or use @pipeline with IO?
237269
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
238270

returns/converters.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# -*- coding: utf-8 -*-
2+
3+
from returns.maybe import Maybe
4+
from returns.result import Failure, Success
5+
6+
7+
def result_to_maybe(result_container):
8+
"""Converts ``Result`` container to ``Maybe`` container."""
9+
return Maybe.new(result_container.value_or(None))
10+
11+
12+
def maybe_to_result(maybe_container):
13+
"""Converts ``Maybe`` container to ``Result`` container."""
14+
inner_value = maybe_container.value_or(None)
15+
if inner_value is not None:
16+
return Success(inner_value)
17+
return Failure(inner_value)

returns/converters.pyi

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# -*- coding: utf-8 -*-
2+
3+
from typing import TypeVar
4+
5+
from returns.maybe import Maybe
6+
from returns.result import Result
7+
8+
_ValueType = TypeVar('_ValueType')
9+
_ErrorType = TypeVar('_ErrorType')
10+
11+
12+
def result_to_maybe(
13+
result_container: Result[_ValueType, _ErrorType],
14+
) -> Maybe[_ValueType]:
15+
...
16+
17+
18+
def maybe_to_result(
19+
maybe_container: Maybe[_ValueType],
20+
) -> Result[_ValueType, None]:
21+
...

returns/maybe.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ def failure(self):
143143

144144
def Some(inner_value): # noqa: N802
145145
"""Public unit function of protected `_Some` type."""
146-
return _Some(inner_value)
146+
return Maybe.new(inner_value)
147147

148148

149149
#: Public unit value of protected `_Nothing` type.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# -*- coding: utf-8 -*-
2+
3+
import pytest
4+
5+
from returns.converters import maybe_to_result
6+
from returns.maybe import Nothing, Some
7+
from returns.result import Failure, Success
8+
9+
10+
@pytest.mark.parametrize('inner_value', [
11+
1,
12+
[],
13+
'',
14+
])
15+
def test_some_to_success(inner_value):
16+
"""Ensures that `Some` is always converted to `Success`."""
17+
assert maybe_to_result(Some(inner_value)) == Success(inner_value)
18+
19+
20+
def test_nothing_to_failure():
21+
"""Ensure that `Nothing` is always converted to `Failure`."""
22+
assert maybe_to_result(Nothing) == Failure(None)
23+
assert maybe_to_result(Some(None)) == Failure(None)
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# -*- coding: utf-8 -*-
2+
3+
import pytest
4+
5+
from returns.converters import result_to_maybe
6+
from returns.maybe import Nothing, Some
7+
from returns.result import Failure, Success
8+
9+
10+
@pytest.mark.parametrize('inner_value', [
11+
1,
12+
[],
13+
'',
14+
])
15+
def test_success_to_some(inner_value):
16+
"""Ensures that `Success` is always converted to `Some`."""
17+
assert result_to_maybe(Success(inner_value)) == Some(inner_value)
18+
19+
20+
def test_success_to_nothing():
21+
"""Ensures that `Success(None_` is always converted to `Nothing`."""
22+
assert result_to_maybe(Success(None)) == Nothing
23+
24+
25+
@pytest.mark.parametrize('inner_value', [
26+
Exception,
27+
0,
28+
None,
29+
])
30+
def test_failure_to_nothing(inner_value):
31+
"""Ensures that `Failure` is always converted to `Nothing`."""
32+
assert result_to_maybe(Failure(inner_value)) == Nothing

tests/test_maybe/test_maybe_equality.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ def test_equality():
1111
assert Nothing is Nothing # noqa: Z312
1212
assert Nothing == _Nothing() == _Nothing(None)
1313
assert Some(5) == Some(5)
14-
assert Some(None) == Some(None)
14+
assert Some(None) == Nothing
1515

1616

1717
def test_nonequality():
@@ -22,7 +22,6 @@ def test_nonequality():
2222
assert _Nothing(None) != None # noqa: E711
2323
assert Some(5) != 5
2424
assert Some(3) is not Some(3)
25-
assert Some(None) != Nothing
2625

2726

2827
def test_is_compare():

tests/test_maybe/test_maybe_new.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,8 @@ def test_maybe_new_some():
1111
def test_maybe_new_nothing():
1212
"""Ensures that `new` works for Nothing container."""
1313
assert Maybe.new(None) == Nothing
14+
15+
16+
def test_some_from_none():
17+
"""Ensure that `Some(None) == Nothing`."""
18+
assert Some(None) == Nothing

0 commit comments

Comments
 (0)