Skip to content

Commit 4df66e9

Browse files
committed
Version 0.10 pre-release
1 parent d55b51c commit 4df66e9

File tree

16 files changed

+472
-300
lines changed

16 files changed

+472
-300
lines changed

CHANGELOG.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@ See (0Ver)[https://0ver.org/].
1515
- **Breaking**: Now `bind` does not change the type of an error
1616
- **Breaking**: Now `rescue` does not change the type of a value
1717
- **Breaking**: Renames `map_failure` to `alt`
18-
- Adds `lift()` function with the ability
19-
to lift function for direct container composition like:
18+
- Adds `box()` function with the ability
19+
to box function for direct container composition like:
2020
`a -> Container[b]` to `Container[a] -> Container[b]`
21-
- Adds `lift_io()` function to lift `a -> a` to `IO[a] -> IO[a]`
21+
- Adds `IO.lift()` function to lift `a -> a` to `IO[a] -> IO[a]`
2222
- Adds `pipe()` function to `pipeline.py`
2323
- Adds `__hash__()` magic methods to all containers
2424

README.md

Lines changed: 47 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,41 @@ Make sure you know how to get started, [check out our docs](https://returns.read
4242

4343
## Contents
4444

45+
- [Maybe container](#maybe-container) that allows you to write `None`-free code
4546
- [Result container](#result-container) that let's you to get rid of exceptions
4647
- [IO marker](#io-marker) that marks all impure operations and structures them
47-
- [Maybe container](#maybe-container) that allows you to write `None`-free code
48+
49+
50+
## Maybe container
51+
52+
`None` is called the [worst mistake in the history of Computer Science](https://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare/).
53+
54+
So, what can we do to check for `None` in our programs?
55+
You can use `Optional` and write a lot of `if some is not None:` conditions.
56+
But, having them here and there makes your code unreadable.
57+
58+
Or you can use
59+
[Maybe](https://returns.readthedocs.io/en/latest/pages/maybe.html) container!
60+
It consists of `Some` and `Nothing` types,
61+
representing existing state and empty (instead of `None`) state respectively.
62+
63+
```python
64+
from typing import Optional
65+
from returns.maybe import Maybe, maybe
66+
67+
@maybe # decorator to convert existing Optional[int] to Maybe[int]
68+
def bad_function() -> Optional[int]:
69+
...
70+
71+
maybe_result: Maybe[float] = bad_function().map(
72+
lambda number: number / 2,
73+
)
74+
# => Maybe will return Some[float] only if there's a non-None value
75+
# Otherwise, will return Nothing
76+
```
77+
78+
You can be sure that `.map()` method won't be called for `Nothing`.
79+
Forget about `None`-related errors forever!
4880

4981

5082
## Result container
@@ -113,7 +145,7 @@ Our code will become complex and unreadable with all this mess!
113145
import requests
114146
from returns.result import Result, safe
115147
from returns.pipeline import pipe
116-
from returns.functions import lift
148+
from returns.functions import box
117149

118150
class FetchUserProfile(object):
119151
"""Single responsibility callable object that fetches user profile."""
@@ -123,7 +155,7 @@ class FetchUserProfile(object):
123155
return pipe(
124156
user_id,
125157
self._make_request,
126-
lift(self._parse_json),
158+
box(self._parse_json),
127159
)
128160

129161
@safe
@@ -137,13 +169,19 @@ class FetchUserProfile(object):
137169
return response.json()
138170
```
139171

140-
Now we have a clean and a safe way to express our business need.
172+
Now we have a clean and a safe and declarative way
173+
to express our business need.
141174
We start from making a request, that might fail at any moment.
175+
Then parsing the response if the request was successful.
176+
And then return the result.
177+
It all happens smoothly due to [pipe](https://returns.readthedocs.io/en/latest/pages/pipeline.html#pipe) function.
178+
179+
We also use [box](https://returns.readthedocs.io/en/latest/pages/functions.html#box) for handy composition.
142180

143181
Now, instead of returning a regular value
144182
it returns a wrapped value inside a special container
145183
thanks to the
146-
[@safe](https://returns.readthedocs.io/en/latest/pages/functions.html#returns.functions.safe)
184+
[@safe](https://returns.readthedocs.io/en/latest/pages/result.html#safe)
147185
decorator.
148186

149187
It will return [Success[Response] or Failure[Exception]](https://returns.readthedocs.io/en/latest/pages/result.html).
@@ -188,7 +226,7 @@ import requests
188226
from returns.io import IO, impure
189227
from returns.result import Result, safe
190228
from returns.pipeline import pipe
191-
from returns.functions import lift, lift_io
229+
from returns.functions import box
192230

193231
class FetchUserProfile(object):
194232
"""Single responsibility callable object that fetches user profile."""
@@ -198,9 +236,9 @@ class FetchUserProfile(object):
198236
return pipe(
199237
user_id,
200238
self._make_request,
201-
# lift: def (Result) -> Result
202-
# lift_io: def (IO[Result]) -> IO[Result]
203-
lift(box(self._parse_json), IO),
239+
# after box: def (Result) -> Result
240+
# after IO.lift: def (IO[Result]) -> IO[Result]
241+
IO.lift(box(self._parse_json)),
204242
)
205243

206244
@impure
@@ -223,39 +261,6 @@ that it does `IO` and might fail.
223261
So, we act accordingly!
224262

225263

226-
## Maybe container
227-
228-
`None` is called the [worst mistake in the history of Computer Science](https://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare/).
229-
230-
So, what can we do?
231-
You can use `Optional` and write a lot of `if some is not None` conditions.
232-
But, having them here and there makes your code unreadable.
233-
234-
Or you can use
235-
[Maybe](https://returns.readthedocs.io/en/latest/pages/maybe.html) container!
236-
It consists of `Some` and `Nothing` types,
237-
representing existing state and empty (instead of `None`) state respectively.
238-
239-
```python
240-
from typing import Optional
241-
from returns.maybe import Maybe, maybe
242-
243-
@maybe
244-
def bad_function() -> Optional[int]:
245-
...
246-
247-
maybe_result: Maybe[float] = bad_function().map(
248-
lambda number: number / 2,
249-
)
250-
# => Maybe will return Some[float] only if there's a non-None value
251-
# Otherwise, will return Nothing
252-
```
253-
254-
It follows the same composition rules as the `Result` type.
255-
You can be sure that `.map()` method won't be called for `Nothing`.
256-
Forget about `None`-related errors forever!
257-
258-
259264
## More!
260265

261266
Want more? [Go to the docs!](https://returns.readthedocs.io)

docs/conf.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@ def _get_project_meta():
7474
'exclude-members': '__dict__,__weakref__',
7575
}
7676

77+
# Set `typing.TYPE_CHECKING` to `True`:
78+
# https://pypi.org/project/sphinx-autodoc-typehints/
79+
set_type_checking_flag = False
80+
7781
# Add any paths that contain templates here, relative to this directory.
7882
templates_path = ['_templates']
7983

docs/index.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@ Contents
1717
:caption: Userguide
1818

1919
pages/container.rst
20-
pages/result.rst
2120
pages/maybe.rst
2221
pages/io.rst
23-
pages/unsafe.rst
22+
pages/result.rst
23+
pages/pipeline.rst
2424
pages/functions.rst
2525

2626
.. toctree::
@@ -31,7 +31,7 @@ Contents
3131

3232

3333
Indices and tables
34-
==================
34+
------------------
3535

3636
* :ref:`genindex`
3737
* :ref:`modindex`

docs/pages/container.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ while maintaining the execution context.
99

1010
List of supported containers:
1111

12+
- :class:`Maybe <returns.maybe.Maybe>` to handle ``None`` cases
1213
- :class:`IO <returns.io.IO>` to mark explicit ``IO`` actions
1314
- :class:`Result <returns.result.Result>` to handle possible exceptions
14-
- :class:`Maybe <returns.maybe.Maybe>` to handle ``None`` cases
1515

1616
We will show you container's simple API of one attribute
1717
and several simple methods.

docs/pages/functions.rst

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,35 @@ functions with one argument and one return to be composed.
2323
Only works with regular functions (not async).
2424

2525

26+
box
27+
---
28+
29+
Without ``box()`` it would be very hard to declaratively compose two entities:
30+
31+
1. Existings container
32+
2. Existing functions that accepts a regular value and returns a container
33+
34+
We can compose these entities with ``.bind`` when calling it directly,
35+
but how can we do it inversevely?
36+
37+
.. code:: python
38+
39+
from returns.functions import box
40+
from returns.maybe import Maybe
41+
42+
def bindable(arg: int) -> Maybe[str]:
43+
...
44+
45+
container: Maybe[int]
46+
# We now have two way of composining these entities.
47+
# 1. Via ``.bind``:
48+
container.bind(bindable) # works!
49+
# 2. Or via ``box``, the same but in the inverse way:
50+
box(bindable)(container)
51+
52+
That's it.
53+
54+
2655
raise_exception
2756
---------------
2857

@@ -65,3 +94,5 @@ API Reference
6594

6695
.. automodule:: returns.functions
6796
:members:
97+
98+
.. autofunction:: returns.functions.box

docs/pages/io.rst

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,27 @@ We can track it, we can fight it, we can design it better.
140140
By saying that, it is assumed that
141141
you have a functional core and imperative shell.
142142

143+
Lifting
144+
~~~~~~~
145+
146+
You can also lift regular function into one
147+
that works with ``IO`` on both ends. It really helps you with the composition!
148+
149+
.. code:: python
150+
151+
def regular_function(arg: int) -> float:
152+
return arg / 2 # not an `IO` operation
153+
154+
container: IO[int]
155+
# When we need to compose `regular_function` with `IO`,
156+
# we have two ways of doing it:
157+
container.map(regular_function)
158+
# or, it is the same as:
159+
IO.lift(regular_function)(container)
160+
161+
The second variant is useful when using :func:`returns.pipeline.pipe`
162+
and other different declarative tools.
163+
143164

144165
impure
145166
------
@@ -173,6 +194,42 @@ if :ref:`decorator_plugin <type-safety>` is used.
173194
This happens due to `mypy issue <https://github.com/python/mypy/issues/3157>`_.
174195

175196

197+
unsafe_perform_io
198+
-----------------
199+
200+
Sometimes you really need to get the raw value from ``IO`` container.
201+
For example:
202+
203+
.. code:: python
204+
205+
def index_view(request, user_id):
206+
user: IO[User] = get_user(user_id)
207+
return render('index.html', { user: user }) # ???
208+
209+
In this case your web-framework will not render your user correctly.
210+
Since it does not expect it to be wrapped inside ``IO`` containers.
211+
And we obviously cannot ``map`` or ``bind`` this function.
212+
213+
What to do? Use :func:`unsafe_perform_io <returns.unsafe.unsafe_perform_io>`:
214+
215+
.. code::
216+
217+
from returns.unsafe import unsafe_perform_io
218+
219+
def index_view(request, user_id):
220+
user: IO[User] = get_user(user_id)
221+
return render('index.html', { user: unsafe_perform_io(user) }) # Ok
222+
223+
We need it as an escape and compatibility mechanism for our imperative shell.
224+
225+
It is recommended
226+
to use `import-linter <https://github.com/seddonym/import-linter>`_
227+
to restrict imports from ``returns.unsafe`` expect the top-level modules.
228+
229+
Inspired by Haskell's
230+
`unsafePerformIO <https://hackage.haskell.org/package/base-4.12.0.0/docs/System-IO-Unsafe.html#v:unsafePerformIO>`_
231+
232+
176233
FAQ
177234
---
178235

@@ -238,3 +295,6 @@ API Reference
238295

239296
.. automodule:: returns.io
240297
:members:
298+
299+
.. automodule:: returns.unsafe
300+
:members:

0 commit comments

Comments
 (0)