Skip to content

Commit 6c5120f

Browse files
committed
Closes #124
1 parent b17c7e8 commit 6c5120f

26 files changed

+357
-198
lines changed

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,21 @@ We follow Semantic Versions since the `0.1.0` release.
77

88
### Features
99

10+
- Now `bind` does not change the type of an error
11+
- Now `rescue` does not change the type of a value
12+
- Renames `map_failure` to `alt`
1013
- Adds `__hash__` magic methods to all containers
1114

15+
### Bugfixes
16+
17+
- Changes `Any` to `NoReturn` in `Success` and `Failure`
18+
- Now all type parameters in `Result`, `Maybe`, and `IO` are covariant
19+
1220
### Misc
1321

1422
- Updates `mypy` version
1523
- Updates `wemake-python-styleguide` and introduces `nitpick`
24+
- Updates `pytest-plugin-mypy`
1625

1726

1827
## 0.9.0

CONTRIBUTING.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ you will need to get familiar with these concepts:
77

88
- http://learnyouahaskell.com/functors-applicative-functors-and-monoids
99
- https://github.com/dbrattli/OSlash/wiki/Functors,-Applicatives,-And-Monads-In-Pictures
10+
- https://gcanti.github.io/fp-ts/
1011

1112
Here are some practical examples of what we are doing here:
1213

README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ plugins =
3535
returns.contrib.mypy.decorator_plugin
3636
```
3737

38+
We also recommend to use the same `mypy` settings [we use](https://github.com/wemake-services/wemake-python-styleguide/blob/master/styles/mypy.toml).
39+
3840
Make sure you know how to get started, [check out our docs](https://returns.readthedocs.io/en/latest/)!
3941

4042

@@ -155,12 +157,11 @@ and wrap it inside a new `Failure[Exception]`!
155157
And we can clearly see all result patterns
156158
that might happen in this particular case:
157159
- `Success[UserProfile]`
158-
- `Failure[HttpException]`
159-
- `Failure[JsonDecodeException]`
160+
- `Failure[Exception]`
160161

161162
And we can work with each of them precisely.
162-
It is a good practice to create `Enum` classes or `Union` types
163-
with a list of all the possible errors.
163+
It is a good practice to create `Enum` classes or `Union` sum type
164+
with all the possible errors.
164165

165166

166167
## IO marker

docs/pages/container.rst

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,12 @@ is used to literally bind two different containers together.
5757
5858
from returns.result import Result, Success
5959
60-
def may_fail(user_id: int) -> Result[int, str]:
60+
def may_fail(user_id: int) -> Result[float, str]:
6161
...
6262
63-
# Can be assumed as either Success[int] or Failure[str]:
64-
result: Result[int, str] = Success(1).bind(may_fail)
63+
value: Result[int, str] = Success(1)
64+
# Can be assumed as either Success[float] or Failure[str]:
65+
result: Result[float, str] = value.bind(may_fail)
6566
6667
And we have :func:`~returns.primitives.container.Mappable.map`
6768
to use containers with regular functions.
@@ -105,7 +106,7 @@ It mean that our code can go on two tracks:
105106
2. Failed one: where something went wrong
106107

107108
We can switch from track to track: we can fail something
108-
or we can rescue the situation.
109+
or we can fix the situation.
109110

110111
.. mermaid::
111112
:caption: Railway oriented programming.
@@ -123,7 +124,7 @@ or we can rescue the situation.
123124
F2 -- Fix --> S3
124125
S3 -- Fail --> F4
125126
S5 -- Fail --> F6
126-
F6 -- Rescue --> S7
127+
F6 -- Fix --> S7
127128

128129
style S1 fill:green
129130
style S3 fill:green
@@ -147,7 +148,7 @@ types like ``Failure``:
147148
transforms error to value (failure became success)
148149
that works only when container is in failed state,
149150
is the opposite of ``map`` method
150-
- :func:`~returns.primitives.container.UnwrapableFailure.map_failure`
151+
- :func:`~returns.primitives.container.UnwrapableFailure.alt`
151152
transforms error to another error
152153
that works only when container is in failed state,
153154
is the opposite of ``map`` method
@@ -162,7 +163,7 @@ during the pipeline execution:
162163
def double(state: int) -> float:
163164
return state * 2.0
164165
165-
result: Result[Any, float] = Failure(1).map_failure(double)
166+
result: Result[Any, float] = Failure(1).alt(double)
166167
# => Failure(2.0)
167168
168169
result: Result[float, int] = Failure(1).fix(double)
@@ -180,14 +181,12 @@ It can also rescue your flow and get on the successful track again:
180181
return Success(0)
181182
return Failure(state)
182183
183-
result: Result[int, Exception] = Failure(
184-
ZeroDivisionError(),
185-
).rescue(tolerate_exception)
184+
value: Result[int, Exception] = Failure(ZeroDivisionError())
185+
result: Result[int, Exception] = value.rescue(tolerate_exception)
186186
# => Success(0)
187187
188-
result2: Result[int, Exception] = Failure(
189-
ValueError(),
190-
).rescue(tolerate_exception)
188+
value2: Result[int, Exception] = Failure(ValueError())
189+
result2: Result[int, Exception] = value2.rescue(tolerate_exception)
191190
# => Failure(ValueError())
192191
193192

docs/pages/functions.rst

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,7 @@ We allow you to do that with ease!
4343
"""Imagine, that you need to reraise ValidationErrors due to API."""
4444
return self._validate_user(
4545
username,
46-
# TODO: change in #84 to `.map_failure()`
47-
).fix(
46+
).alt(
4847
# What happens here is interesting, since you do not let your
4948
# unwrap to fail with UnwrapFailedError, but instead
5049
# allows you to reraise a wrapped exception.

docs/pages/result.rst

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,37 @@ since it will raise ``TypeError`` somewhere
3232
and other ``None`` exception-friends.
3333

3434

35+
FAQ
36+
---
37+
38+
How to create unit objects?
39+
~~~~~~~~~~~~~~~~~~~~~~~~~~~
40+
41+
Use ``Success`` or ``Failure`` together with the explicit annotation.
42+
Python's type system does not allow us to do much, so this is required:
43+
44+
.. code:: python
45+
46+
def callback(arg: int) -> Result[float, int]:
47+
return Success(float(arg))
48+
49+
first: Result[int, int] = Success(1)
50+
first.bind(callback)
51+
52+
Otherwise it would raise a ``mypy`` error:
53+
54+
.. code:: python
55+
56+
first = Success(1)
57+
first.bind(callback)
58+
# Argument 1 to "bind" of "Result" has incompatible type
59+
# "Callable[[int], Result[float, int]]";
60+
# expected "Callable[[int], Result[float, NoReturn]]"
61+
62+
This happens because ``mypy`` is unable to implicitly
63+
cast ``NoReturn`` to any other type.
64+
65+
3566
is_successful
3667
-------------
3768

returns/io.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
from returns.primitives.container import BaseContainer
1010

11-
_ValueType = TypeVar('_ValueType')
11+
_ValueType = TypeVar('_ValueType', covariant=True)
1212
_NewValueType = TypeVar('_NewValueType')
1313

1414
# Helpers:

returns/maybe.py

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
Callable,
99
Coroutine,
1010
Generic,
11+
NoReturn,
1112
Optional,
1213
TypeVar,
1314
Union,
@@ -19,10 +20,13 @@
1920
from returns.primitives.container import BaseContainer
2021
from returns.primitives.exceptions import UnwrapFailedError
2122

22-
# Aliases:
23-
_ValueType = TypeVar('_ValueType')
23+
# Definitions:
24+
_ValueType = TypeVar('_ValueType', covariant=True)
2425
_NewValueType = TypeVar('_NewValueType')
25-
_ErrorType = TypeVar('_ErrorType')
26+
27+
# Aliases:
28+
_FirstType = TypeVar('_FirstType')
29+
_SecondType = TypeVar('_SecondType')
2630

2731

2832
class Maybe(
@@ -113,7 +117,7 @@ def __init__(self, inner_value: None = None) -> None:
113117
"""
114118
Wraps the given value in the Container.
115119
116-
'value' can only be ``None``.
120+
``inner_value`` can only be ``None``.
117121
"""
118122
BaseContainer.__init__(self, inner_value) # noqa: WPS609
119123

@@ -221,26 +225,26 @@ def Some(inner_value: Optional[_ValueType]) -> Maybe[_ValueType]: # noqa: N802
221225

222226

223227
#: Public unit value of protected `_Nothing` type.
224-
Nothing: Maybe[Any] = _Nothing()
228+
Nothing: Maybe[NoReturn] = _Nothing()
225229

226230

227231
@overload
228232
def maybe( # type: ignore
229233
function: Callable[
230234
...,
231-
Coroutine[_ValueType, _ErrorType, Optional[_NewValueType]],
235+
Coroutine[_FirstType, _SecondType, Optional[_ValueType]],
232236
],
233237
) -> Callable[
234238
...,
235-
Coroutine[_ValueType, _ErrorType, Maybe[_NewValueType]],
239+
Coroutine[_FirstType, _SecondType, Maybe[_ValueType]],
236240
]:
237241
"""Case for async functions."""
238242

239243

240244
@overload
241245
def maybe(
242-
function: Callable[..., Optional[_NewValueType]],
243-
) -> Callable[..., Maybe[_NewValueType]]:
246+
function: Callable[..., Optional[_ValueType]],
247+
) -> Callable[..., Maybe[_ValueType]]:
244248
"""Case for regular functions."""
245249

246250

returns/primitives/container.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ def fix(
111111

112112

113113
@runtime
114-
class Rescueable(Protocol[_ValueType, _ErrorType]):
114+
class Rescueable(Protocol[_NewValueType, _ErrorType]):
115115
"""
116116
Represents a "context" in which calculations can be executed.
117117
@@ -159,7 +159,7 @@ def unwrap(self) -> _ValueType:
159159
class UnwrapableFailure(Protocol[_ValueType, _ErrorType]):
160160
"""Allows to unwrap failures."""
161161

162-
def map_failure(
162+
def alt(
163163
self,
164164
function: Callable[[_ErrorType], _NewErrorType],
165165
) -> 'Fixable[_ValueType, _NewErrorType]':

0 commit comments

Comments
 (0)