Although this does not change proposed concept, this was useful to clarify definitions.
So to summarise. Definitions:
a) function composition:
* f ⚬ g ⚬ h == h(g(f(x)))
b) function piping: function composition in reverse order
* h >> g >> f == f ⚬ g ⚬ h == h(g(f(x)))
c) iterator piping: function piping, where payload is an iterator
and functions are iterator processors
(consumers and most of the time producers too)
d) Function application: https://en.wikipedia.org/wiki/Apply
* g @@ f @@ x == g(f(x)) == (f ⚬ g)(x) # Forward
* x |> f |> g == g(f(x)) == (f ⚬ g)(x) # Inverse
Now, if this was to be integral part of Python language, there are various options:
a) FunctionType (and all callables) implement operators.
b) Operators are implemented at parser level
c) Object oriented approach such as pipelines - Rust
d) Custom statement implementation
In case of (a) or (b), one could do something similar to:
x = range(100)
result = (
x
|> partial(map, lambda x: x*2)
|> list
)
# OR
result = x |> (
partial(map, lambda x: x*2)
>> list
)
In case of (c) it would be similar to:
x = range(100)
result = Pipeline(x).map(lambda x: x*2).to_list()
The drawback of this approach is that at least with this design function composition can not be re-used for other inputs.
In case of (d) it is hard to say. But some options were explored in Introduce funnel operator i,e '|>' to allow for generator pipelines.
Now…
All of the above options are heavy and I don’t think the cost of those can be justified.
In contrast, this proposal allows user to have very similar convenience with significantly smaller addition to CPython: pipe object.
Then, user customise pipe to have similar syntax to the (a) and (b) examples above:
x = range(100)
result = (
x
| pipe(partial(map, lambda x: x*2))
| pipe(list)
)
# OR
result = x | (pipe()
>> partial(map, lambda x: x*2)
>> list
)
Also, it can achieve (c) by implementing extra methods to pipe, so that the user can do:
result = x | pipe.from_map(lambda x: x*2).to_list()
So this proposal is not aiming to implement specific approach, but rather aims to implement smallest possible component that would allow user to build on top of it. This component benefits from being in stdlib, because:
a) User reaps performance benefits - performant __call__
b) Does not need to write boilerplate and can build on top of class, which is well integrated to the rest of Python’s stdlib. E.g. has correct signature & __get__ method.
Use Cases
- function piping needs:
- @sayandipdutta’s reply only scratches the surface of various bigger frameworks having their own implementation: `functools.pipe` - Function Composition Utility - #28 by sayandipdutta
- These are some libraries that are explicitly aimed at pipeline implementation:
- More performant identity function:
- github search:
/lambda x: x( )?(\)|,)/- 128k files
- github search:
- Predicate composition:
- github search (only a small fraction):
/lambda x: x\[.*\](\.[^\d\W]\w*)/- 57k files - Using
pipeis arguably less readable, but would provide better performance in certain cases where it results in faster code and is needed.
- github search (only a small fraction):