--- Title: Releasing pipe v2.0 Summary: Introducing partially parametrized pipes! --- # Introducing partially parametrized pipes! [pipe](https://github.com/JulienPalard/Pipe) is a very old project of mine, proudly showing 1.3k stars on github! I don't maintain it a lot, but today I'm announcing a new release introducing a nice feature: partially parametrized pipes. If you're familiar with [currying](https://en.wikipedia.org/wiki/Currying) or [functools.partial](https://docs.python.org/3/library/functools.html#functools.partial) you won't get lost. ## Show me! Before `pipe 2`, the following were already valid: ```python >>> from random import randint >>> from pipe import where, sort >>> >>> negative = where(lambda x: x < 0) >>> positive = where(lambda x: x > 0) >>> >>> numbers = [randint(-10, 10) for _ in range(10)] >>> >>> numbers | positive | sort [7, 10] >>> numbers | negative | sort [-10, -9, -8, -7, -4, -4, -2] ``` It allows to **name things**, naming things make code more readable so I wanted more of it. It was probably underrated, I like to do this kind of things: ```python >>> isort = sort(key=lambda x: x**2) >>> numbers | isort [0, -2, -4, -4, -7, 7, -8, -9, 10, -10] ``` But it failed as soon as you try go deeper: ```python >>> numbers | isort(reverse=True) Traceback (most recent call last): File "", line 1, in File "pipe.py", line 100, in __ror__ return self.function(other) File "pipe.py", line 103, in return Pipe(lambda x: self.function(x, *args, **kwargs)) TypeError: Pipe.__call__..() got an unexpected keyword argument 'reverse' ``` Call it a bug, that's what's pipe 2 fixes: ```python >>> from pipe import where, sort >>> from random import randint >>> numbers = [randint(-10, 10) for _ in range(10)] >>> >>> isort = sort(key=lambda x: x**2) >>> numbers | isort(reverse=True) [-10, -9, 8, -6, 6, -4, 4, 4, 2, 1] >>> ``` Or in the other way around: ```python >>> rsort = sort(reverse=True) >>> numbers | rsort [8, 6, 4, 4, 2, 1, -4, -6, -9, -10] >>> numbers | rsort(key=lambda x: x**2) [-10, -9, 8, -6, 6, -4, 4, 4, 2, 1] ``` From here you can build many specialized blocks from a single pipe, here's a better example: ```python import re from pipe import Pipe @Pipe def grep(iterable, pattern, flags=0, invert=False): """Mimics grep.""" for line in iterable: match = re.match(pattern, line, flags=flags) if (match and not invert) or (not match and invert): yield line ``` Build the small reusable blocks: ```python igrep = grep(flags=re.I) vgrep = grep(invert=True) vigrep = igrep(invert=True) https = igrep("https://") not_https = https(invert=True) ... ``` And they all do what you think they do: ```python >>> lines = ["https://python.org", "http://detectportal.firefox.com/", "Just no an URL", "HTTPS://afpy.org"] >>> for url in lines | https: ... print(url) ... https://python.org HTTPS://afpy.org ``` ```python >>> for url in lines | not_https: ... print(url) ... http://detectportal.firefox.com/ Just no an URL ``` Remember I said currying? Yes you can abuse the syntax, but please don't: ```python >>> from pipe import sort >>> lines | grep()(invert=True)()(flags=re.I)()("https://")() | sort ['Just not an URL', 'http://detectportal.firefox.com/'] ``` As I like to say: « It's not because it's possible that you should do it ».