Skip to content

Commit 20f8d59

Browse files
committed
Refs #224
1 parent aa6130b commit 20f8d59

File tree

17 files changed

+263
-38
lines changed

17 files changed

+263
-38
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ See [0Ver](https://0ver.org/).
4848
- Adds `Immutable` primitive type
4949
- Adds `Unitable` protocol and `.from_success()` and `.from_failure()`
5050
methods for all `Result` realted classes
51+
- Adds `flow` function, which is similar to `pipe`
5152

5253

5354
### Bugfixes

README.md

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -272,15 +272,16 @@ Our code will become complex and unreadable with all this mess!
272272
```python
273273
import requests
274274
from returns.result import Result, safe
275-
from returns.pipeline import pipe
275+
from returns.pipeline import flow
276276
from returns.pointfree import bind
277277

278278
def fetch_user_profile(user_id: int) -> Result['UserProfile', Exception]:
279279
"""Fetches `UserProfile` TypedDict from foreign API."""
280-
return pipe(
280+
return flow(
281+
user_id,
281282
_make_request,
282283
bind(_parse_json),
283-
)(user_id)
284+
)
284285

285286
@safe
286287
def _make_request(user_id: int) -> requests.Response:
@@ -308,7 +309,7 @@ thanks to the
308309
decorator. It will return [Success[YourType] or Failure[Exception]](https://returns.readthedocs.io/en/latest/pages/result.html).
309310
And will never throw exception at us!
310311

311-
We also use [pipe](https://returns.readthedocs.io/en/latest/pages/pipeline.html#pipe)
312+
We also use [flow](https://returns.readthedocs.io/en/latest/pages/pipeline.html#flow)
312313
and [bind](https://returns.readthedocs.io/en/latest/pages/pointfree.html#bind)
313314
functions for handy and declarative composition.
314315

@@ -394,19 +395,20 @@ explicit!
394395
import requests
395396
from returns.io import IO, IOResult, impure_safe
396397
from returns.result import safe
397-
from returns.pipeline import pipe
398+
from returns.pipeline import flow
398399
from returns.pointfree import bind
399400

400401
def fetch_user_profile(user_id: int) -> IOResult['UserProfile', Exception]:
401402
"""Fetches `UserProfile` TypedDict from foreign API."""
402-
return pipe(
403+
return flow(
404+
user_id,
403405
_make_request,
404406
# before: def (Response) -> UserProfile
405407
# after safe: def (Response) -> ResultE[UserProfile]
406408
# after bind: def (ResultE[Response]) -> ResultE[UserProfile]
407409
# after lift: def (IOResultE[Response]) -> IOResultE[UserProfile]
408410
IOResult.lift_result(bind(_parse_json)),
409-
)(user_id)
411+
)
410412

411413
@impure_safe
412414
def _make_request(user_id: int) -> requests.Response:

docs/pages/pipeline.rst

Lines changed: 60 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,55 +9,89 @@ composition easy, readable, pythonic, and useful.
99
Let's start with the first one.
1010

1111

12+
flow
13+
----
14+
15+
``flow`` allows to easily compose multiple functions together.
16+
It is useful when you already have an instance to compose functions with.
17+
18+
Let's see an example.
19+
20+
.. code:: python
21+
22+
>>> from returns.pipeline import flow
23+
>>> assert flow(
24+
... [1, 2, 3],
25+
... lambda collection: max(collection),
26+
... lambda max_number: -max_number,
27+
... ) == -3
28+
29+
Use it when you need to compose a lot of functions together.
30+
31+
And now let's get to know ``pipe``, it is very similar,
32+
but has different usage pattern.
33+
34+
1235
.. _pipe:
1336

1437
pipe
1538
----
1639

1740
``pipe`` is an easy way to compose functions together.
41+
It is useful when you don't have an instance to compose functions with yet.
42+
1843
Let's see an example.
1944

2045
.. code:: python
2146
2247
>>> from returns.pipeline import pipe
2348
24-
>>> pipe(str, lambda x: x + 'b', str.upper)(1)
25-
'1B'
49+
>>> pipeline = pipe(str, lambda x: x + 'b', str.upper)
50+
>>> assert pipeline(1) == '1B'
2651
27-
There's also a way to compose containers together:
52+
It might be later used with multiple values:
2853

2954
.. code:: python
3055
31-
from returns.pipeline import pipe
32-
from returns.result import Result
33-
from returns.functions import box
34-
35-
def regular_function(arg: int) -> float:
36-
...
56+
>>> assert pipeline(2) == '2B'
3757
38-
def returns_container(arg: float) -> Result[complex, ValueError]:
39-
...
58+
It is also might be useful to compose containers together:
4059

41-
def also_returns_container(args: complex) -> Result[str, ValueError]:
42-
...
60+
.. code:: python
4361
44-
pipe(
45-
regular_function, # composes easily
46-
returns_container, # also composes easily, but returns a container...
47-
# So we need to `box` the next function to allow it to consume
48-
# the container from the previous step.
49-
box(also_returns_container),
50-
)(1) # we provide the initial value itself as a argument to `pipe(...)`
51-
# => Will return `Result[str, ValueError]` as declared in the last step
62+
>>> from returns.pipeline import pipe
63+
>>> from returns.result import Result, Success, Failure
64+
>>> from returns.pointfree import bind
65+
66+
>>> def regular_function(arg: int) -> float:
67+
... return float(arg)
68+
...
69+
70+
>>> def returns_container(arg: float) -> Result[str, ValueError]:
71+
... if arg != 0:
72+
... return Success(str(arg))
73+
... return Failure(ValueError())
74+
...
75+
76+
>>> def also_returns_container(arg: str) -> Result[str, ValueError]:
77+
... return Success(arg + '!')
78+
...
79+
80+
>>> transaction = pipe(
81+
... regular_function, # composes easily
82+
... returns_container, # also composes easily, but returns a container
83+
... # So we need to `bind` the next function to allow it to consume
84+
... # the container from the previous step.
85+
... bind(also_returns_container),
86+
... )
87+
>>> result = transaction(1) # running the pipeline
88+
>>> assert result == Success('1.0!')
5289
5390
You might consider ``pipe()`` as :func:`returns.functions.compose` on steroids.
5491
The main difference is that ``compose`` takes strictly two arguments
5592
(or you might say that it has an arity of two),
5693
while ``pipe`` has infinite possible arguments.
5794

58-
See also :func:`returns.io.IO.lift` which is also extremely
59-
helpful for ``IO`` composition.
60-
6195
Limitations
6296
~~~~~~~~~~~
6397

@@ -69,6 +103,8 @@ But, composition with ``pipe`` is limited to two things:
69103
2. It is flexible. Sometimes you might need more power.
70104
Use ``@pipeline`` in this case!
71105

106+
In contrast ``flow`` does not have these problems.
107+
72108

73109
.. _pipeline:
74110

docs/pages/result.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ Composition
4040
-----------
4141

4242
Make sure to check out how to compose container with
43-
:ref:`pipe` and :ref:`@pipeline <pipeline>`!
43+
``flow``, :ref:`pipe` and :ref:`@pipeline <pipeline>`!
4444
Read more about them if you want to compose your containers easily.
4545

4646

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# -*- coding: utf-8 -*-

returns/_generated/pipeline/flow.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# -*- coding: utf-8 -*-
2+
3+
from functools import reduce
4+
5+
from returns._generated.pipeline import pipe
6+
7+
8+
def _flow(instance, *functions):
9+
"""
10+
Allows to compose a value and up to multiple functions that use this value.
11+
12+
All starts with the value itself.
13+
Each next function uses the previous result as an input parameter.
14+
15+
This function is closely related
16+
to :func:`pipe <returns._generated.pipeline.pipe._pipe>`
17+
and solves several typing related issues.
18+
19+
Here's how it should be used:
20+
21+
.. code:: python
22+
23+
>>> from returns.pipeline import flow
24+
25+
# => executes: str(float(int('1')))
26+
>>> assert flow('1', int, float, str) == '1.0'
27+
28+
See also:
29+
- https://stackoverflow.com/a/41585450/4842742
30+
- https://github.com/gcanti/fp-ts/blob/master/src/pipeable.ts
31+
32+
"""
33+
return pipe._pipe(*functions)(instance)

returns/_generated/pipeline/flow.pyi

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
# -*- coding: utf-8 -*-
2+
3+
from typing import Callable, TypeVar, overload
4+
5+
_T1 = TypeVar('_T1')
6+
_T2 = TypeVar('_T2')
7+
_T3 = TypeVar('_T3')
8+
_T4 = TypeVar('_T4')
9+
_T5 = TypeVar('_T5')
10+
_T6 = TypeVar('_T6')
11+
_T7 = TypeVar('_T7')
12+
_T8 = TypeVar('_T8')
13+
_T9 = TypeVar('_T9')
14+
15+
16+
@overload
17+
def _flow(
18+
instance: _T1,
19+
p1: Callable[[_T1], _T2],
20+
p2: Callable[[_T2], _T3],
21+
) -> _T3:
22+
...
23+
24+
25+
@overload
26+
def _flow(
27+
instance: _T1,
28+
p1: Callable[[_T1], _T2],
29+
p2: Callable[[_T2], _T3],
30+
p3: Callable[[_T3], _T4],
31+
) -> _T4:
32+
...
33+
34+
35+
@overload
36+
def _flow(
37+
instance: _T1,
38+
p1: Callable[[_T1], _T2],
39+
p2: Callable[[_T2], _T3],
40+
p3: Callable[[_T3], _T4],
41+
p4: Callable[[_T4], _T5],
42+
) -> _T5:
43+
...
44+
45+
46+
@overload
47+
def _flow(
48+
instance: _T1,
49+
p1: Callable[[_T1], _T2],
50+
p2: Callable[[_T2], _T3],
51+
p3: Callable[[_T3], _T4],
52+
p4: Callable[[_T4], _T5],
53+
p5: Callable[[_T5], _T6],
54+
) -> _T6:
55+
...
56+
57+
58+
@overload
59+
def _flow(
60+
instance: _T1,
61+
p1: Callable[[_T1], _T2],
62+
p2: Callable[[_T2], _T3],
63+
p3: Callable[[_T3], _T4],
64+
p4: Callable[[_T4], _T5],
65+
p5: Callable[[_T5], _T6],
66+
p6: Callable[[_T6], _T7],
67+
) -> _T7:
68+
...
69+
70+
71+
@overload
72+
def _flow(
73+
instance: _T1,
74+
p1: Callable[[_T1], _T2],
75+
p2: Callable[[_T2], _T3],
76+
p3: Callable[[_T3], _T4],
77+
p4: Callable[[_T4], _T5],
78+
p5: Callable[[_T5], _T6],
79+
p6: Callable[[_T6], _T7],
80+
p7: Callable[[_T7], _T8],
81+
) -> _T8:
82+
...
83+
84+
85+
@overload
86+
def _flow(
87+
instance: _T1,
88+
p1: Callable[[_T1], _T2],
89+
p2: Callable[[_T2], _T3],
90+
p3: Callable[[_T3], _T4],
91+
p4: Callable[[_T4], _T5],
92+
p5: Callable[[_T5], _T6],
93+
p6: Callable[[_T6], _T7],
94+
p7: Callable[[_T7], _T8],
95+
p8: Callable[[_T8], _T9],
96+
) -> _T9:
97+
...

returns/_generated/pipe.py renamed to returns/_generated/pipeline/pipe.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,15 @@ def _pipe(*functions):
99
"""
1010
Allows to compose a value and up to 7 functions that use this value.
1111
12-
Each next function uses the previos result as an input parameter.
12+
Each next function uses the previous result as an input parameter.
1313
Here's how it should be used:
1414
1515
.. code:: python
1616
1717
>>> from returns.pipeline import pipe
1818
1919
# => executes: str(float(int('1')))
20-
>>> pipe(int, float, str)('1')
21-
'1.0'
20+
>>> assert pipe(int, float, str)('1') == '1.0'
2221
2322
A friendly hint: do not start ``pipe`` definition with ``lambda`` function.
2423
``mypy`` will complain: ``error: Cannot infer type argument 1 of "_pipe"``.
@@ -29,6 +28,7 @@ def _pipe(*functions):
2928
3029
1. Use regular annotated functions
3130
2. Type the variable itself: ``user: Callable[[int], float] = pipe(...)``
31+
3. Use :func:`flow <returns._generated.pipeline.flow._flow>` function
3232
3333
See also:
3434
- https://stackoverflow.com/a/41585450/4842742
File renamed without changes.

0 commit comments

Comments
 (0)