summaryrefslogtreecommitdiff
path: root/python-nonion.spec
diff options
context:
space:
mode:
authorCoprDistGit <infra@openeuler.org>2023-05-31 07:18:14 +0000
committerCoprDistGit <infra@openeuler.org>2023-05-31 07:18:14 +0000
commit31bbf4cf31281be2909f0519d4d2b45af659f9ea (patch)
tree379338282982eade7aef46d56c79166c696de805 /python-nonion.spec
parent7d19577fab944240449823c0cebddd1518bf22d7 (diff)
automatic import of python-nonion
Diffstat (limited to 'python-nonion.spec')
-rw-r--r--python-nonion.spec5046
1 files changed, 5046 insertions, 0 deletions
diff --git a/python-nonion.spec b/python-nonion.spec
new file mode 100644
index 0000000..68c19ce
--- /dev/null
+++ b/python-nonion.spec
@@ -0,0 +1,5046 @@
+%global _empty_manifest_terminate_build 0
+Name: python-nonion
+Version: 0.4.4
+Release: 1
+Summary: Python Functional Programming for Humans.
+License: GNU Lesser General Public License v3 (LGPLv3)
+URL: https://bitbucket.org/shkroba/nonion
+Source0: https://mirrors.nju.edu.cn/pypi/web/packages/f7/72/81e8bc2dbb61761b13c7dabda055296a0c32ef50749c69e5d103d0538d2e/nonion-0.4.4.tar.gz
+BuildArch: noarch
+
+
+%description
+# NOnion
+
+NOnion is a Python package that provides tools for Functional Programming. One of its aims is to eliminate nested function calls such as **z(g(f(x)))** which remind an __onion__.
+
+# Installing
+
+```bash
+pip install nonion
+```
+
+# Tutorial
+
+NOnion contains a set of functions and types that __might__ simplify your workflow with Functional Programming in Python. Those tools are designed (but not limited) to work with *Function* and *Pipeline* wrappers.
+
+* *Function* - a wrapper of **any** Python *Callable*,
+* *Pipeline* - a wrapper of **any** Python *Iterable*.
+
+It is important to understand that *NOnion* provides tools used for FP in context of Python. Because it is impossible to fully implement some constructs from FP languages in Python, *NOnion* provides tools that resemble some of those constructs.
+
+## *Function*
+
+In order to create a *Function*, you simply pass any *Callable*:
+
+```python
+f = Function(lambda x: x + 1)
+f(5) # 6
+```
+
+You can also create an identity *Function*:
+
+```python
+g = Function()
+```
+
+Notice, that a *Function* takes exactly single value and returns exactly single value.
+
+### *compose*
+
+A ``Function composition" defined as $( f \circ g )(x) = f(g(x))$ could be done in the following way:
+
+```python
+z = f @ g
+
+# alternatively
+
+z = f.compose(g)
+```
+
+You can also use *compose* several times:
+
+```python
+z = f @ g @ f
+```
+
+Instead of wrapping each *Callable* with a *Function*, you can wrap only __first__ *Callable* and use *compose* on the rest.
+
+```python
+def f(x):
+ return x + 1
+
+g = Function() @ (lambda x: x * 2) @ f
+g(5) # 12
+```
+
+### *then*
+
+Function composition sometimes might be hard to read, because you have to read it from right-to-left.
+In order to achieve better readability, you can use *then*.
+
+```python
+g = Function() / (lambda x: x * 2) / f
+g(5) # 11
+
+# alternatively
+
+g = Function().then(lambda x: x * 2).then(f)
+g(5) # 11
+```
+
+### *fanout*
+
+If you need to pass an argument to two functions, you can use *fanout*:
+
+```python
+g = Function() / (lambda x: x + 1) & (lambda x: x * 2)
+g(5) # (6, 10)
+
+mean = (Function() / sum & len) / star(op.truediv)
+mean([1, 2, 3]) # 2.0
+
+# alternatively
+
+g = Function().then(lambda x: x + 1).fanout(lambda x: x * 2)
+g(5) # (6, 10)
+
+mean = (Function().then(sum).fanout(len)).then(star(op.truediv))
+mean([1, 2, 3]) # 2.0
+```
+
+### *split*
+
+If you need to apply first value of a pair to a first function and a second value of the pair to a second function, you can use *split*:
+
+```python
+g = Function() / (lambda x: x + 1) ^ (lambda x: x * 2)
+g((2, 3)) # (3, 6)
+
+teams = {"team a": ["member 1", "member 2"], "team b": ["member 3"]}
+f = Function() / str.capitalize ^ len
+for t in teams.items():
+ print(f(t))
+
+# ('Team a', 2)
+# ('Team b', 1)
+
+# alternatively
+
+g = Function().then(lambda x: x + 1).split(lambda x: x * 2)
+g((2, 3)) # (3, 6)
+
+f = Function().then(str.capitalize).split(len)
+for t in teams.items():
+ print(f(t))
+
+# ('Team a', 2)
+# ('Team b', 1)
+```
+
+### *call*
+
+Sometimes you want to call a function ``inline'' after several compositions. In this case, you might use:
+
+```python
+(Function() / (lambda x: x * 2) / f)(5) # 11
+```
+
+But it might be hard to read. Especially, when you mostly pass lambdas. A better way to call a function is by using:
+
+```python
+Function() / (lambda x: x * 2) / f | 5 # 11
+```
+
+### *star* (function)
+
+Suppose, that you defined a function with multiple arguments such as:
+
+```python
+def f(x, y):
+ return x + y * x
+```
+
+And you want to wrap that function using Function. In this case, you have to use *star*.
+
+```python
+Function() @ star(f) | (1, 2) # 3
+```
+
+*star* simply passes arguments to a function using Python *\** (star) operator.
+
+### *unstar* (function)
+
+*unstar* is the opposite function to *star*:
+
+```python
+names = unstar(", ".join)("Haskell Curry", "John Smith", "George Sand")
+print(names) # Haskell Curry, John Smith, George Sand
+```
+
+### *foreach*
+
+You can also call a function for each value in some *Iterable* in the following way:
+
+```python
+ys = Function() / (lambda x: x * 2) / (lambda x: x + 1) * range(5)
+
+for y in ys:
+ print(y)
+
+# 1
+# 3
+# 5
+# 7
+# 9
+#
+```
+
+## *Pipeline*
+
+In order to create a *Pipeline*, you simply pass any *Iterable*:
+
+```python
+xs = Pipeline(range(5))
+
+# notation abuse, do not use that:
+
+xs = Function() / Pipeline | range(5)
+```
+
+You can also create an empty *Pipeline*:
+
+```python
+xs = Pipeline()
+```
+
+Under the hood *Pipeline* is simply uses *iter* on a passed *Iterable*. It means, that if you will pass an *Iterable*, that could be exhausted, you iterate over *Pipeline* only once.
+
+```python
+xs = Pipeline(range(2))
+
+for x in xs:
+ print(x)
+
+# 1
+# 2
+#
+
+# perfectly fine, because range(x) returns a special object
+for x in xs:
+ print(x)
+
+# 1
+# 2
+#
+
+xs = Pipeline(x for x in range(2))
+
+for x in xs:
+ print(x)
+
+# 1
+# 2
+#
+
+# xs already exhausted
+for x in xs:
+ print(x)
+```
+
+### *map*
+
+*map* allows you to call a *Callable*, which takes a single value and returns a single value, on each value of the *Pipeline*.
+
+```python
+ys = Pipeline(range(3)) / (lambda x: x + 1) / (lambda x: (x, x + 1)) / star(lambda x, y: x + y * x)
+
+for y in ys:
+ print(y)
+
+# 3
+# 8
+# 15
+#
+
+# alternatively
+
+ys = Pipeline(range(3)).map(lambda x: x + 1).map(lambda x: (x, x + 1)).map(star(lambda x, y: x + y * x))
+```
+
+### *filter*
+
+*filter* allows you to filter *Pipeline* values.
+
+```python
+ys = Pipeline(range(3)) % (lambda x: x > 1)
+
+for y in ys:
+ print(y)
+
+# 2
+#
+
+# alternatively
+
+ys = Pipeline(range(3)).filter(lambda x: x > 1)
+```
+
+### *flatmap*
+
+*flatmap* allows you to call a *Callable*, which takes a single value and returns an *Iterable*, on each value of the *Pipeline* and flatten results into single *Pipeline*.
+
+```python
+ys = Pipeline(range(2)) / (lambda x: x + 1) * (lambda x: (x, x + 1))
+
+for y in ys:
+ print(y)
+
+# 1
+# 2
+# 2
+# 3
+#
+
+# alternatively
+
+ys = Pipeline(range(2)).map(lambda x: x + 1).flatmap(lambda x: (x, x + 1))
+```
+
+### *apply*
+
+*apply* allows you to call a *Callable*, which takes an *Iterable* and returns an *Iterable*, on whole *Pipeline*.
+
+```python
+ys = Pipeline(range(2)) / (lambda x: x + 1) // tuple # internally Pipeline now has a tuple
+
+for y in ys:
+ print(y)
+
+# 1
+# 2
+#
+
+# now multiple itertations is possible
+for y in ys:
+ print(y)
+
+# 1
+# 2
+#
+
+# alternatively
+
+ys = Pipeline(range(2)).map(lambda x: x + 1).apply(tuple)
+```
+
+### *collect*
+
+*collect* allows you to call a *Callable*, which takes an *Iterable* and returns any single value, on whole *Pipeline*. The difference between *apply* and *collect* is that *collect* returns the result of a function instead of wrapping it with *Pipeline*.
+
+```python
+ys = Pipeline(range(2)) / (lambda x: x + 1) >> tuple
+print(ys)
+
+# (1, 2)
+#
+
+# alternatively
+
+ys = Pipeline(range(2)).map(lambda x: x + 1).collect(tuple)
+```
+
+You can also combine *collect* with any function which takes an *Iterator*:
+
+```python
+ys = Pipeline(range(2)) / (lambda x: x + 1) >> next_
+print(ys) # (1,)
+
+ys = Pipeline(range(2)) % (lambda x: x == 5) >> next_
+print(ys) # ()
+
+ys = Pipeline(range(5)) >> shift(islice, 2)
+
+for y in ys:
+ print(y)
+
+# 0
+# 1
+
+# alternatively you can use apply
+
+ys = Pipeline(range(5)) // shift(islice, 2) | print
+
+# 0
+# 1
+```
+
+### *foreach*
+
+*foreach* allows you to call a *Callable*, which takes a single value, on each value of the *Pipeline*.
+
+```python
+Pipeline(range(2)) / (lambda x: x + 1) | print
+
+# 1
+# 2
+#
+
+# alternatively
+
+Pipeline(range(2)).map(lambda x: x + 1).foreach(print)
+```
+
+## *groupon*
+
+*groupon* is a function which takes a function *Callable[[X], Y]*, and returns some function which takes *Iterable[X]* and returns *Iterable[X]* grouped on *Callable[[X], Y]* function. The *groupon* function uses Python *groupby* function under the hood. *groupon* adds a grouping key using passed *Callable[[X], Y]* function and sorts values by that key before applying *groupby*.
+
+```python
+xs = -3, 1, 0, -1, 5
+
+(
+ Pipeline(xs)
+ // groupon(lambda x: x > 0)
+ / value(tuple)
+ | print
+)
+
+# (False, (-3, 0, -1))
+# (True, (1, 5))
+```
+
+## *nonion.tools*
+
+### *Either*
+
+*Either* is a type alias. *Either* is defined as follows:
+
+```python
+Either = Tuple[Maybe[X], Maybe[Y]]
+```
+
+*Either* can be used when you need to return either left (bad) value or a right (good) value:
+
+```python
+def readline(path: str) -> Either[str, str]:
+ h: Maybe[IOBase] = try_(open)(path)
+
+ if not h:
+ return (("error occurred during open",), ())
+
+ h, *_ = h
+ line = h.readline()
+ h.close()
+
+ return ((), line)
+
+error, line = readline("requirements.txt")
+
+if line:
+ print(*line)
+else:
+ print(*error)
+```
+
+Because *Either* is simply a type alias, it does not checks whether only left or only right value is passed.
+
+### *Maybe*
+
+*Maybe* is a type alias. *Maybe* resembles Haskell's *Maybe* in Python. *Maybe* is defined as follows:
+
+```python
+Maybe = Union[Tuple[X], Tuple[()]]
+```
+
+As we can see *Maybe* is simply some *tuple* that might contain a single value or be an empty *tuple*.
+It means that in order to initialize an *Maybe* you can simply write:
+
+```python
+x = () # empty Maybe
+y = (3,) # Maybe with value 3
+```
+
+You can easily check whether an *Maybe* is empty:
+
+```python
+def f(x: int) -> Maybe[int]:
+ return (x,) if x < 3 else ()
+
+x: Maybe[int] = f(5)
+
+if not x:
+ print("Maybe is empty") # Maybe is empty
+```
+
+You can also provide an alternative value if *Maybe* is empty and immediately try to unwrap the *Maybe*:
+
+```python
+x: Maybe[int] = f(5)
+y, *_ = x or (42,)
+
+print(y) # 42
+```
+
+```python
+# alternatively
+
+x: Maybe[int] = f(1)
+z = x or (42,)
+
+# notice: if you pass an empty *z to a single argument function, you will get an error
+print(*z) # 1
+```
+
+Because *Maybe* is simply a *tuple* under the hood, you can apply any Python function (that operates on *tuple*) to an instance of an *Maybe*.
+
+### *as_catch*
+
+*as_catch* is simply:
+
+```python
+@curry
+def as_catch(default: Callable[[X], Y], xys: Iterable[Tuple[X, Y]]) -> Callable[[X], Y]:
+ return catch(as_match(xys), default=default)
+```
+
+Example of *as_catch* usage:
+
+```python
+successor: Callable[[int], int] = Pipeline(range(10)) // zipmapr(lambda x: x + 1) >> as_catch(lambda _: -1)
+print(successor(1)) # 2
+print(successor(100)) # -1
+```
+
+### *as_match*
+
+*as_match* is simply:
+
+```python
+def as_match(xys: Iterable[Tuple[X, Y]]) -> Callable[[X], Maybe[Y]]:
+ x_to_y = dict(xys)
+
+ def lookup(x: X) -> Maybe[Y]:
+ return (x_to_y[x],) if x in x_to_y else ()
+
+ return lookup
+```
+
+Example of *as_match* usage:
+
+```python
+successor: Callable[[int], Maybe[int]] = Pipeline(range(10)) // zipmapr(lambda x: x + 1) >> as_match
+print(successor(1)) # (2,)
+print(successor(100)) # ()
+```
+
+### *between*
+
+*between* is simply:
+
+```python
+def between(low: float, high: float) -> Callable[[float], bool]:
+ return lambda x: low <= x and x <= high
+```
+
+Example of *between* usage:
+
+```python
+ys = filter(between(3, 5), range(10))
+print(tuple(ys)) # (3, 4, 5)
+```
+
+### *both*
+
+*both* is a function that takes a function *Callable[[X], Y]* and returns a function *Callable[[Tuple[X, X]], Tuple[Y, Y]]*. The returned function takes a pair and applies *Callable[[X], Y]* to both values.
+
+```python
+both(lambda x: x + 1)((1, 2)) # (2, 3)
+```
+
+### *cache*
+
+*cache* is a decorator which returns a function that always returns a value that was returned in the first call.
+
+```python
+def f(x: int) -> int:
+ return x + 5
+
+g = cache(f)
+print(g(5)) # 10
+print(g()) # 10
+print(g("abc", 1, {})) # 10
+
+h = cache(f)
+print(h(7)) # 12
+```
+
+### *catch*
+
+*catch* is a function that resembles pattern-matching in Python. It takes some functions `*fs: Callable[..., Maybe[Y]]` with some catch-all function `default: Callable[..., Y]` and returns a function `Callable[..., Y]` which executes `fs` functions one by one until some function will return non-empty `Maybe[Y]`. If none of those functions will return a non-empty `Maybe[Y]`, the result of `default` function is returned.
+
+```python
+# let's say that we want to parse age ranges that we have in our data:
+age_ranges = (
+ "10-20",
+ "20-30",
+ "30+",
+ "60+",
+ "invalid input"
+)
+
+# we consider 30+ to be a valid range <30, 100)
+
+def parse_range(x: str) -> Tuple[int, int]:
+ raw = x.split("-")
+ low, high, *_ = map(int, raw)
+
+ return low, high
+
+def parse_unbounded_range(x: str) -> Tuple[int, int]:
+ raw, *_ = x.split("+")
+ return int(raw), 100
+
+# we will use <18, 100) as our default range
+parse = catch(
+ try_(parse_range),
+ try_(parse_unbounded_range),
+ default=lambda _: (18, 100)
+)
+
+for x in age_ranges:
+ print(parse(x))
+
+# (10, 20)
+# (20, 30)
+# (30, 100)
+# (60, 100)
+# (18, 100)
+```
+
+### *compose*
+
+*compose* is an implementation of a ``Function composition" defined as $( f \circ g )(x) = f(g(x))$.
+
+```python
+xs = "a", "ab", "c"
+yxs = enumerate(xs)
+
+p: Callable[[Tuple[int, str]], bool] = compose(lambda x: x.startswith("a"), snd)
+filtered: Iterable[Tuple[int, str]] = filter(p, yxs)
+
+ys = map(fst, filtered)
+print(tuple(ys)) # (0, 1)
+```
+
+### *cons*
+
+*cons* allows you to prepend value of type *X* to an *Iterable[X]*.
+
+```python
+print(tuple(cons(1)((2, 3, 4, 5)))) # (1, 2, 3, 4, 5)
+```
+
+### *const*
+
+*const* is a function that takes a value of type *X* and returns a function of type *Callable[[Y], X]*. The returned function will ignore its argument and will return the value that was passed to *const*.
+
+```python
+print(const(1)("abc")) # 1
+```
+
+### *curry*
+
+*curry* is simply:
+
+```python
+def curry(f: Callable[..., Y]) -> Callable[..., Y]:
+ return lambda *args, **kwargs: partial(f, *args, **kwargs)
+```
+
+### *cycle*
+
+*cycle* is a function which takes *Tuple[X, ...]* and returns *Iterable[X]*. *Iterable[X]* is created by repeatedly yielding elements from passed *Tuple[X, ...]*.
+
+```python
+xs = take(10)(cycle([1, 2, 3]))
+print(tuple(xs)) # (1, 2, 3, 1, 2, 3, 1, 2, 3, 1)
+```
+
+### *drop*
+
+*drop* is simply:
+
+```python
+def drop(n: int) -> Callable[[Iterable[X]], Iterable[X]]:
+ return (lambda xs: islice(xs, n, None)) if n > 0 else (lambda _: ())
+```
+
+Example of *drop* usage:
+
+```python
+xs = drop(1)(range(3))
+print(tuple(xs)) # (1, 2)
+
+xs = islice(range(3), 1, None)
+print(tuple(xs)) # (1, 2)
+```
+
+### *either*
+
+*either* is a function that takes a function of type *Callable[[X], Z]* and a function of type *Callable[[Y], Z]* and returns a function of type *Callable[[Either[X, Y]], Z]*. The returned function takes *Either[X, Y]*. If the *Either* contains left value, the value will be applied to *Callable[[X], Z]*. If the *Either* contains right value, the value will be applied to *Callable[[Y], Z]*. The result of application is returned.
+
+```python
+f = either(lambda x: f"Error: {x}", lambda y: f"OK: {y}")
+print(f((("unable to parse",), ()))) # Error: unable to parse
+print(f(((), (1,)))) # OK: 1
+```
+
+### *either_to_maybe*
+
+*either_to_maybe* is an alias for *snd*.
+
+### *except_*
+
+*except_* is a decorator which returns a function that returns *Either* with some value or an *Exception* that was raised.
+
+```python
+f = except_(next)
+xs = iter(range(2))
+
+print(f(xs)) # ((), (0,))
+print(f(xs)) # ((), (1,))
+print(f(xs)) # ((StopIteration(),), ())
+```
+
+### *fail*
+
+*fail* is a function which takes a function *Callable[[Exception], Y]* and returns a decorator which takes a function *Callable[..., Y]* and returns *Callable[..., Y]*. The function returned by the decorator uses passed *Callable[[Exception], Y]* to handle possible errors produced by a decorated function. If no errors produced, *Callable[[Exception], Y]* will not be executed and the result of the decorated function will be returned.
+
+```python
+# Let's say that you want to write is_repeated function
+# which tells you whether you have a collection consisting
+# only from the single value.
+
+# The simplest function you could think of might look like this:
+
+def is_repeated(xs: Iterable[X]) -> bool:
+ x, *rest = xs
+ return all(x == y for y in rest)
+
+# It works on collections that have at least one value:
+
+print(is_repeated((1, 1))) # True
+print(is_repeated((1, 2, 3))) # False
+
+# but when you have an empty collection, this function will result
+# in an error:
+
+print(is_repeated(()))
+# ValueError: not enough values to unpack (expected at least 1, got 0)
+
+# In order to handle this case, you can rewrite this function in a
+# following manner:
+
+def is_repeated(xs: Iterable[X]) -> bool:
+ xs = iter(xs)
+ wrapped_x = next_(xs)
+
+ if wrapped_x:
+ x, *_ = wrapped_x
+ return all(x == y for y in xs)
+ else: return True
+
+# And it would work:
+
+print(is_repeated((1, 1))) # True
+print(is_repeated((1, 2, 3))) # False
+print(is_repeated(())) # True
+
+# You might also use a *fail* function which will surround your
+# function with try-except clause, to deal with empty collection.
+
+@fail(lambda _: True)
+def is_repeated(xs: Iterable[X]) -> bool:
+ x, *rest = xs
+ return all(x == y for y in rest)
+
+# In case when error is raised by is_repeated, the
+# lambda _: True
+# function will be executed. The raised error will be passed to
+# that function.
+
+def g(e: Exception) -> bool:
+ print(e)
+ return True
+
+@fail(g)
+def is_repeated(xs: Iterable[X]) -> bool:
+ x, *rest = xs
+ return all(x == y for y in rest)
+
+print(is_repeated((1,))) # True
+print(is_repeated(()))
+# not enough values to unpack (expected at least 1, got 0)
+# True
+```
+
+### *find*
+
+*find* is a function which takes a predicate and returns a function which takes some *Iterable* and returns an *Maybe* with value that matches the predicate if such value exists:
+
+```python
+x: Maybe[int] = find(lambda x: x == 3)(range(5))
+print(x) # (3,)
+
+x: Maybe[int] = find(lambda x: x == -1)(range(5))
+print(x) # ()
+```
+
+### *findindex*
+
+*findindex* is a function that works like *find*, but instead of returning a function which returns a value in *Iterable* that matches some predicate, it returns a function which returns an index of that value in *Iterable*.
+
+```python
+x: Maybe[int] = findindex(lambda x: x == 8)(range(5, 10))
+print(x) # (3,)
+
+x: Maybe[int] = findindex(lambda x: x == -1)(range(5, 10))
+print(x) # ()
+```
+
+### *finds*
+
+*finds* is a function which takes an *Iterable[Callable[[X], bool]]* of predicates and returns a function which takes some *Iterable[X]* and returns an *Iterable[Maybe[X]]*. *finds* iterates over each predicate and searches for a matching value for that predicate in the passed *Iterable[X]*. *finds* will store checked *Iterable[X]* values in a buffer, so that the buffer will be checked at first and (if needed) the remaining *Iterable[X]* will be checked at last.
+
+```python
+fs = (lambda x: x == 2), (lambda x: x == 4), (lambda x: x == 1), (lambda x: x == -1)
+ys: Iterable[Maybe[int]] = finds(fs)(range(5))
+
+for y in ys:
+ print(y)
+
+# (2,)
+# (4,)
+# (1,)
+# ()
+```
+
+### *flattenl*
+
+*flattenl* is a function which takes a *Tuple* which contains another *Tuple* on the beginning and flattens that inner *Tuple* inside of outer *Tuple*.
+
+```python
+xys = {"A": 2.5, "B": 3.14}
+Pipeline(xys.items()) // zipr(count(1)) / flattenl | print
+
+# ('A', 2.5, 1)
+# ('B', 3.14, 2)
+```
+
+### *flattenr*
+
+*flattenr* is a function which takes a *Tuple* which contains another *Tuple* on the end and flattens that inner *Tuple* inside of outer *Tuple*.
+
+```python
+xys = {"A": 2.5, "B": 3.14}
+Pipeline(xys.items()) // zipl(count(1)) / flattenr | print
+
+# (1, 'A', 2.5)
+# (2, 'B', 3.14)
+```
+
+### *flip*
+
+*flip* is simply:
+
+```python
+def flip(f: Callable[[Y, X], Z]) -> Callable[[X, Y], Z]:
+ return lambda x, y: f(y, x)
+```
+
+Example of *flip* usage:
+
+```python
+xs = "A", "B", "C"
+Pipeline(enumerate(xs)) / key(lambda x: x + 1) * star(flip(repeat)) | print
+
+# A
+# B
+# B
+# C
+# C
+# C
+```
+
+### *foldl*
+
+*foldl* is a function which takes a binary function *Callable[[Y, X], Y]* and some accumulator *Y* and returns a function which takes *Iterable[X]* and returns *Y*. This function allows you to fold *Iterable[X]* from __left__ using passed binary function. The accumulator is being passed as the __first__ argument of the binary function.
+
+Example of *foldl* usage:
+
+```python
+xs = range(ord("A"), ord("Z") + 1)
+alphabet = Pipeline(xs) / chr >> foldl(operator.add, "")
+
+print(alphabet)
+
+# ABCDEFGHIJKLMNOPQRSTUVWXYZ
+```
+
+### *foldl1*
+
+*foldl1* is a similar function to *foldl*. The difference between *foldl1* and *foldl* is that *foldl1* takes *Callable[[X, X], X]*, uses *Iterable[X]* __first__ element as the accumulator and returns *X*. *foldl1* will raise an error if the supplied *Iterable[X]* is empty.
+
+### *foldr*
+
+*foldr* is a function which takes a binary function *Callable[[X, Y], Y]* and some accumulator *Y* and returns a function which takes *Iterable[X]* and returns *Y*. This function allows you to fold *Iterable[X]* from __right__ using passed binary function. The accumulator is being passed as the __last__ argument of the binary function.
+
+Example of *foldr* usage:
+
+```python
+xs = range(ord("A"), ord("Z") + 1)
+reversed_alphabet = (
+ Pipeline(xs)
+ / chr
+ // foldr(lambda x, acc: acc + [x], [])
+ >> foldl(operator.add, "")
+)
+
+print(reversed_alphabet)
+
+# ZYXWVUTSRQPONMLKJIHGFEDCBA
+```
+
+### *foldr1*
+
+*foldr1* is a similar function to *foldr*. The difference between *foldr1* and *foldr* is that *foldr1* takes *Callable[[X, X], X]*, uses *Iterable[X]* __last__ element as the accumulator and returns *X*. *foldr1* will raise an error if the supplied *Iterable[X]* is empty. Under the hood *foldr1* will use *tuple* on passed *Iterable[X]* in order to extract the accumulator.
+
+### *fst*
+
+*fst* is simply:
+
+```python
+def fst(xy: Tuple[X, Y]) -> X:
+ return xy[0]
+```
+
+### *group*
+
+*group* is a function which takes *Iterable[X]* and returns *Iterable[Tuple[X, ...]]*. This function groups passed elements by equality comparison `==`.
+
+```python
+xs = 1, 1, 2, 2, 2, 3, 1, 1, 1
+print(tuple(group(xs))) # ((1, 1), (2, 2, 2), (3,), (1, 1, 1))
+```
+
+### *groupby*
+
+*groupby* is a function which takes an equality comparison function *Callable[[X, X], bool]* and returns a function *Callable[[Iterable[X]], Iterable[Tuple[X, ...]]]* which groups passed elements by the equality comparison function.
+
+```python
+people = (
+ ("Alex", 23),
+ ("John", 23),
+ ("Sam", 27),
+ ("Kate", 27),
+ ("Fred", 23),
+)
+
+grouped = groupby(lambda x, y: snd(x) == snd(y))(people)
+print(tuple(grouped))
+# ((('Alex', 23), ('John', 23)), (('Sam', 27), ('Kate', 27)), (('Fred', 23),))
+
+# or you can use *on* function:
+
+grouped = groupby(on(operator.eq, snd))(people)
+print(tuple(grouped))
+# ((('Alex', 23), ('John', 23)), (('Sam', 27), ('Kate', 27)), (('Fred', 23),))
+```
+
+### *in_*
+
+*in_* is simply:
+
+```python
+def in_(xs: Tuple[X, ...]) -> Callable[[X], bool]:
+ return lambda x: x in xs
+```
+
+### *key*
+
+*key* is simply:
+
+```python
+def key(f: Callable[[X], Z]) -> Callable[[Tuple[X, Y]], Tuple[Z, Y]]:
+ g: Callable[[Tuple[X, Y]], Z] = compose(f, fst)
+ return lambda xy: (g(xy), snd(xy))
+```
+
+Example of *key* usage:
+
+```python
+xys = {"A": [1, 2, 3], "B": [3, 4]}
+zys = map(key(str.casefold), xys.items())
+
+for zy in zys:
+ print(zy)
+
+# ('a', [1, 2, 3])
+# ('b', [3, 4])
+```
+
+### *length*
+
+*length* is a function which takes an *Iterable* and returns number of elements in that *Iterable*. *length* exhausts the *Iterable*.
+
+```python
+xs = 1, 2, 3
+print(len(xs)) # 3
+
+# len(iter(xs)) will raise an error
+print(length(iter(xs))) # 3
+```
+
+### *lift*
+
+*lift* is simply:
+
+```python
+lift = curry(map)
+```
+
+### *match_*
+
+*match_* is a function that resembles pattern-matching in Python. It takes some functions `*fs: Callable[..., Maybe[Y]]` and returns a function `Callable[..., Maybe[Y]]` which executes `fs` functions one by one until some function will return non-empty `Maybe[Y]`. If none of those functions will return a non-empty `Maybe[Y]`, an empty `Maybe[Y]` (i.e. `()`) is returned.
+
+```python
+# let's say that we want to parse age ranges that we have in our data:
+age_ranges = (
+ "10-20",
+ "20-30",
+ "30+",
+ "60+",
+ "invalid input"
+)
+
+# we consider 30+ to be a valid range <30, 100)
+
+def parse_range(x: str) -> Tuple[int, int]:
+ raw = x.split("-")
+ low, high, *_ = map(int, raw)
+
+ return low, high
+
+def parse_unbounded_range(x: str) -> Tuple[int, int]:
+ raw, *_ = x.split("+")
+ return int(raw), 100
+
+parse = match_(
+ try_(parse_range),
+ try_(parse_unbounded_range)
+)
+
+for x in age_ranges:
+ print(parse(x))
+
+# ((10, 20),)
+# ((20, 30),)
+# ((30, 100),)
+# ((60, 100),)
+# ()
+```
+
+### *maybe*
+
+*maybe* is a function that takes a function of type *Callable[[], Y]* and a function of type *Callable[[X], Y]* and returns a function of type *Callable[[Maybe[X]], Y]*. The returned function takes *Maybe[X]*. If the *Maybe* contains value, the value will be applied to *Callable[[X], Y]* and a result of application is returned. If the *Maybe* contains no value, the *Callable[[], Y]* is called and a result is returned.
+
+```python
+f = maybe(lambda: "Error", lambda x: f"OK: {x}")
+print(f(())) # Error
+print(f((1,))) # OK: 1
+```
+
+### *maybe_to_either*
+
+*maybe_to_either* is a function which allows you to create an *Either[Y, X]* from an *Maybe[X]*. *maybe_to_either* takes a function *Callable[[], Y]* and returns a function *Callable[[Maybe[X]], Either[Y, X]]*.
+
+```python
+raw_numbers = "1\n22\nten\n333".splitlines()
+
+xs = (
+ Pipeline(raw_numbers)
+ / try_(int)
+ / maybe_to_either(lambda: f"Failed to parse.")
+ | print
+)
+
+# ((), (1,))
+# ((), (22,))
+# (('Failed to parse.',), ())
+# ((), (333,))
+```
+
+The difference between using *maybe_to_either* and explicitly creating *Either* using tuples is that *maybe_to_either* will not evaluate the left part if the right part is present. That is why *Callable[[], Y]* is being passed to *maybe_to_either* instead of *Y*.
+
+### *merge*
+
+*merge* is a function which takes two sorted *Iterable[X]* and merges them into single sorted *Iterable[X]*. It uses *lambda x, y: x <= y* comparison function. Use *key* parameter to specify a function *Callable[[X], Y]* to be called on each element prior to making comparisons.
+
+```python
+xs = merge((1, 3, 5), (1, 2, 4))
+print(tuple(xs)) # (1, 1, 2, 3, 4, 5)
+
+xs, ys = [(1, "a"), (3, "d"), (5, "f")], [(1, "b"), (2, "c"), (4, "e")]
+print(tuple((merge(xs, ys, key=fst))))
+# ((1, 'a'), (1, 'b'), (2, 'c'), (3, 'd'), (4, 'e'), (5, 'f'))
+```
+
+### *next_*
+
+*next_* is simply:
+
+```python
+next_: Callable[[Iterator[X]], Maybe[X]] = try_(next)
+```
+
+Example of *next_* usage:
+
+```python
+xs = iter(range(2))
+
+print(next_(xs)) # (0,)
+print(next_(xs)) # (1,)
+print(next_(xs)) # ()
+```
+
+### *not_*
+
+*not_* is a function which takes a predicate and returns negation of that predicate.
+
+```python
+print(not_(lambda x, y: x == y)(1, 5)) # True
+```
+
+### *on*
+
+*on* is simply:
+
+```python
+def on(f: Callable[[Y, Y], Z], g: Callable[[X], Y]) -> Callable[[X, X], Z]:
+ return lambda p, n: f(g(p), g(n))
+```
+
+Example of *on* usage could be found in *groupby* section.
+
+### *padl*
+
+*padl* is a function which allows you to pad some *Iterable* from the __left__ using a filler. This function takes a number *n* and a filler *x*. In case when **exact=False** option is passed, it returns a function which prepends *n - k* fillers to the passed *Iterable*, where *k* is a length of the passed *Iterable*. In case when **exact=True** option is passed, it returns a function which prepends __exactly__ *n* fillers to the passed *Iterable*.
+
+```python
+xs = "".join(padl(5, "x")("abc"))
+print(xs) # xxabc
+
+xs = "".join(padl(5, "x", exact=True)("abc"))
+print(xs) # xxxxxabc
+```
+
+### *padr*
+
+*padr* is a function which allows you to pad some *Iterable* from the __right__ using a filler. This function takes a number *n* and a filler *x*. In case when **exact=False** option is passed, it returns a function which appends *n - k* fillers to the passed *Iterable*, where *k* is a length of the passed *Iterable*. In case when **exact=True** option is passed, it returns a function which appends __exactly__ *n* fillers to the passed *Iterable*.
+
+```python
+xs = "".join(padr(5, "x")("abc"))
+print(xs) # abcxx
+
+xs = "".join(padr(5, "x", exact=True)("abc"))
+print(xs) # abcxxxxx
+```
+
+### *partition*
+
+*partition* is a function which takes a predicate and returns a function *Callable[[Iterable[X]], Tuple[Tuple[X, ...], Tuple[X, ...]]]*. This returned function splits passed elements into those that do match the predicate and the rest. The difference between *span* and *partition* is that *span* stops when it finds the first element that does not match the predicate and *partition* goes until the end.
+
+```python
+xs = 1, 1, 2, 2, 2, 3, 1, 1, 1
+matched, rest = partition(lambda x: x == 1)(xs)
+
+print(matched) # (1, 1, 1, 1, 1)
+print(rest) # (2, 2, 2, 3)
+```
+
+### *peek*
+
+*peek* is a decorator which allows you to apply some function to a passed argument and return back the passed argument instead of a function's result.
+
+```python
+x = peek(print)(5) # 5
+print(x) # 5
+
+xs = (
+ Pipeline(range(3, 0, -1))
+ / peek(print, "Countdown:", file=sys.stderr)
+ >> tuple
+)
+# Countdown: 3
+# Countdown: 2
+# Countdown: 1
+
+print(xs) # (3, 2, 1)
+```
+
+### *pick*
+
+*pick* is a function which takes some aggregate function *Callable[[Tuple[X, ...]], X]* and returns a function *Callable[[Iterable[X]], Iterable[X]]*. This returned function __picks__ all elements from the passed collection *Iterable[X]* which are equal to the value returned by the aggregate function. The equal function might be substituted with any other function of type signature *Callable[[X, X], bool]* by passing a *compare* parameter.
+
+```python
+print(min([1, 2, 1, 1, 3, 1])) # 1
+
+ys = tuple(pick(min)([1, 2, 1, 1, 3, 1]))
+print(ys) # (1, 1, 1, 1)
+
+print(min([]))
+# ValueError: min() arg is an empty sequence
+
+ys = tuple(pick(min)([]))
+print(ys) # ()
+```
+
+### *pickby*
+
+*pickby* is a function which takes a function *Callable[[X], Y]*, an aggregate function *Callable[[Tuple[Y, ...]], Y]* and returns a function *Callable[[Iterable[X]], Iterable[X]]*. This returned function __picks__ all elements from the passed collection *Iterable[X]* which corresponding *Y* values, created by the *Callable[[X], Y]* function, are equal to the value returned by the aggregate function. The equal function might be substituted with any other function of type signature *Callable[[Y, Y], bool]* by passing a *compare* parameter. The function *Callable[[X], Y]* will be used exactly once on the whole collection.
+
+```python
+cars_and_prices = (
+ ("Audi", 25000),
+ ("BMW", 70000),
+ ("Mercedes", 25000),
+)
+
+cheapest_car = min(cars_and_prices, key=snd)
+print(cheapest_car) # ('Audi', 25000)
+
+cheapest_cars = pickby(snd, min)(cars_and_prices)
+print(tuple(cheapest_cars)) # (('Audi', 25000), ('Mercedes', 25000))
+```
+
+### *powerset*
+
+*powerset* is a function which takes a *Tuple[X, ...]* and produces power set of those elements in form of *Iterable[Iterable[X]]*.
+
+```python
+xs = tuple(range(3))
+ps = tuple(map(tuple, powerset(xs)))
+
+print(ps) # ((), (2,), (1,), (1, 2), (0,), (0, 2), (0, 1), (0, 1, 2))
+```
+
+### *replicate*
+
+*replicate* is a function which takes a number *n* and returns a function, which takes some value *x* and repeats *n* times value *x*.
+
+```python
+xs = tuple(replicate(5)("hello"))
+print(xs)
+# ('hello', 'hello', 'hello', 'hello', 'hello')
+```
+
+### *reverse*
+
+*reverse* is a function which takes an *Iterable[X]* and returns *Deque[X]* which contains elements from *Iterable[X]* in reversed order.
+
+```python
+xs = range(ord("A"), ord("Z") + 1)
+
+reversed_alphabet = (
+ Pipeline(xs)
+ / chr
+ // reverse # Python reversed would not work on Iterable
+ >> foldl(operator.add, "")
+)
+
+print(reversed_alphabet)
+
+# ZYXWVUTSRQPONMLKJIHGFEDCBA
+```
+
+### *scanl*
+
+*scanl* is a similar function to *foldl*. The difference between *scanl* and *foldl* is that *scanl* instead of returning a function which takes *Iterable[X]* and returns *Y*, returns a function which takes *Iterable[X]* and returns *Iterable[Y]*. The resulting *Iterable[Y]* contains all accumulators used in *foldl*.
+
+```python
+xs = scanl(operator.mul, 1)((1, 2, 3, 4, 5))
+print(tuple(xs))
+# (1, 1, 2, 6, 24, 120)
+```
+
+### *scanl1*
+
+*scanl1* is a similar function to *foldl1*. The difference between *scanl1* and *foldl1* is that *scanl1* instead of returning a function which takes *Iterable[X]* and returns *X*, returns a function which takes *Iterable[X]* and returns *Iterable[X]*. The resulting *Iterable[X]* contains all accumulators used in *foldl1*.
+
+```python
+xs = scanl1(operator.mul)((1, 2, 3, 4, 5))
+print(tuple(xs))
+# (1, 2, 6, 24, 120)
+```
+
+### *scanr*
+
+*scanr* is a similar function to *foldr*. The difference between *scanr* and *foldr* is that *scanr* instead of returning a function which takes *Iterable[X]* and returns *Y*, returns a function which takes *Iterable[X]* and returns *Deque[Y]*. The resulting *Deque[Y]* contains all accumulators used in *foldr*.
+
+```python
+xs = scanr(operator.mul, 1)((1, 2, 3, 4, 5))
+print(xs)
+# deque([120, 120, 60, 20, 5, 1])
+```
+
+### *scanr1*
+
+*scanr1* is a similar function to *foldr1*. The difference between *scanr1* and *foldr1* is that *scanr1* instead of returning a function which takes *Iterable[X]* and returns *X*, returns a function which takes *Iterable[X]* and returns *Deque[X]*. The resulting *Deque[X]* contains all accumulators used in *foldr1*.
+
+```python
+xs = scanr1(operator.mul)((1, 2, 3, 4, 5))
+print(xs)
+# deque([120, 120, 60, 20, 5])
+```
+
+### *search*
+
+*search* is a function which takes a predicate *Callable[[X], bool]* along with *Iterable[Y]* and returns a function which takes *Iterable[X]* and returns *Iterable[Y]*. This function zips *Iterable[Y]* with *Iterable[X]* and returns those *Y*s for which corresponding *X*s match the predicate.
+
+```python
+xs = search(lambda x: x > 3, count())(range(1, 6))
+print(tuple(xs))
+# (3, 4)
+```
+
+### *shift*
+
+*shift* is a decorator which returns a partially applied function. The difference between Python's *functools.partial* and *shift* is that *shift* will return a function which prepends *\*args* and *\*\*kwargs*:
+
+```python
+def dummy(*args: object, **kwargs: object):
+ print(args)
+ print(kwargs)
+
+partial(dummy, 1, 2, a=1, b="b")(3, 4, c="c")
+print("-" * 10)
+shift(dummy, 1, 2, a=1, b="b")(3, 4, c="c")
+
+# (1, 2, 3, 4)
+# {'a': 1, 'b': 'b', 'c': 'c'}
+# ----------
+# (3, 4, 1, 2)
+# {'c': 'c', 'a': 1, 'b': 'b'}
+```
+
+Example of *shift* usage:
+
+```python
+take_3 = shift(islice, 3)
+xs: Iterable[int] = take_3(range(5))
+
+for x in xs:
+ print(x)
+
+# 0
+# 1
+# 2
+```
+
+### *slide*
+
+*slide* is a function which takes a sliding window length **n** and a **step**, and returns a function which takes an *Iterable* and applies sliding window over it resulting in an *Iterable* of *tuple*s. Each *tuple* has at most length equal to **n**. In case when **exact=True** option is passed, each *tuple* has length equal to **n**. **step** is simply a shift of a sliding window.
+
+```python
+xs: Iterable[Tuple[int, ...]] = slide()(range(10))
+print(tuple(xs))
+# ((0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 7), (7, 8), (8, 9), (9,))
+
+xs: Iterable[Tuple[int, ...]] = slide(n=3, step=2)(range(10))
+print(tuple(xs))
+# ((0, 1, 2), (2, 3, 4), (4, 5, 6), (6, 7, 8), (8, 9))
+
+xs: Iterable[Tuple[int, ...]] = slide(n=3, step=2, exact=True)(range(10))
+print(tuple(xs))
+# ((0, 1, 2), (2, 3, 4), (4, 5, 6), (6, 7, 8))
+
+def is_sorted(xs: Iterable[X], compare: Callable[[X, X], bool] = operator.le) -> bool:
+ return (
+ Pipeline(slide(exact=True)(xs))
+ / star(compare)
+ >> all
+ )
+
+print(is_sorted((1, 2, 5))) # True
+print(is_sorted((1, 2, -5))) # False
+```
+
+### *snd*
+
+*snd* is simply:
+
+```python
+def snd(xy: Tuple[X, Y]) -> Y:
+ return xy[1]
+```
+
+### *span*
+
+*span* is a function which takes a predicate and returns a function *Callable[[Iterable[X]], Tuple[Tuple[X, ...], Iterable[X]]]*. This returned function splits passed elements into those that do match the predicate on the beginning and the rest.
+
+```python
+xs = 1, 1, 2, 2, 2, 3, 1, 1, 1
+matched, rest = span(lambda x: x == 1)(xs)
+
+print(matched) # (1, 1)
+print(tuple(rest)) # (2, 2, 2, 3, 1, 1, 1)
+```
+
+### *splitat*
+
+*splitat* is a function which takes an index *i* and returns a function which splits an *Iterable[X]* into *Tuple[X, ...]* and *Iterable[X]*. The *Tuple[X, ...]* will contain first *i* elements and the *Iterable[X]* will contain the rest.
+
+```python
+xs, rest = splitat(1)(range(5))
+print(xs) # (0,)
+print(tuple(rest)) # (1, 2, 3, 4)
+```
+
+### *strip*
+
+*strip* is a function which takes an *Iterable[X]* and returns an *Iterable[X]* with removed consecutive duplicates. *strip* functions uses only equality comparison `==`.
+
+```python
+xs = 1, 1, 2, 2, 2, 3, 1, 1, 1
+print(tuple(strip(xs))) # (1, 2, 3, 1)
+```
+
+### *stripby*
+
+*stripby* is a function which takes an equality comparison function *Callable[[X, X], bool]* and returns a function *Callable[[Iterable[X]], Iterable[X]]* which removes consecutive duplicates in terms of the equality comparison function.
+
+```python
+people = (
+ ("Alex", 23),
+ ("John", 23),
+ ("Sam", 27),
+ ("Kate", 27),
+ ("Fred", 23),
+)
+
+stripped = stripby(lambda x, y: snd(x) == snd(y))(people)
+print(tuple(stripped))
+# (('Alex', 23), ('Sam', 27), ('Fred', 23))
+
+# or you can use *on* function:
+
+stripped = stripby(on(operator.eq, snd))(people)
+print(tuple(stripped))
+# (('Alex', 23), ('Sam', 27), ('Fred', 23))
+```
+
+### *take*
+
+*take* is simply:
+
+```python
+def take(n: int) -> Callable[[Iterable[X]], Iterable[X]]:
+ return (lambda xs: islice(xs, n)) if n > 0 else (lambda _: ())
+```
+
+Example of *take* usage:
+
+```python
+xs = take(1)(range(3))
+print(tuple(xs)) # (0,)
+
+xs = islice(range(3), 1)
+print(tuple(xs)) # (0,)
+```
+
+### *try_*
+
+*try_* is a decorator which returns a function that returns *Maybe* with some value or an empty *Maybe* if an *Exception* was raised.
+
+```python
+load_json = try_(json.loads)
+
+print(load_json("{}")) # ({},)
+print(load_json("[1, 2, 3]")) # ([1, 2, 3],)
+print(load_json("abc")) # ()
+```
+
+### *unfoldr*
+
+*unfoldr* is a function that takes a function of type *Callable[[X], Maybe[Tuple[Y, X]]]* and returns a function of type *Callable[[X], Iterable[Y]]*. The returned function will repeatedly apply *X* to the function passed to *unfoldr* until it returns value *()*.
+
+```python
+until_3 = unfoldr(lambda acc: ((acc, acc + 1),) if acc < 3 else ())
+print(tuple(until_3(0))) # (0, 1, 2)
+```
+
+### *value*
+
+*value* is simply:
+
+```python
+def value(f: Callable[[Y], Z]) -> Callable[[Tuple[X, Y]], Tuple[X, Z]]:
+ g: Callable[[Tuple[X, Y]], Z] = compose(f, snd)
+ return lambda xy: (fst(xy), g(xy))
+```
+
+Example of *value* usage:
+
+```python
+xys = {"A": [1, 2, 3], "B": [3, 4]}
+xzs = map(value(len), xys.items())
+
+for xz in xzs:
+ print(xz)
+
+# ('A', 3)
+# ('B', 2)
+```
+
+### *where*
+
+*where* is a similar function to *findindex*. The difference between *where* and *findindex* is that *where* returns indices of all elements that match given predicate instead of one. The other difference is that *where* returns a function which takes *Iterable[X]* and returns *Iterable[Y]*, on the other hand *findindex* returns a function which takes *Iterable[X]* and returns *Maybe[int]*.
+
+```python
+xs = where(lambda x: x >= 8)(range(5, 10))
+print(tuple(xs)) # (3, 4)
+
+xs = where(lambda x: x == -1)(range(5, 10))
+print(tuple(xs)) # ()
+```
+
+### *zipflatl*
+
+*zipflatl* is a function which takes a function *Callable[[X], Maybe[Y]]*, and returns some function which takes *Iterable[X]* and returns *Iterable[Tuple[X, Y]]* with only those elements from *Iterable[X]* that are mapped to non-empty *Maybe[Y]* by the function *Callable[[X], Maybe[Y]]*.
+
+```python
+xs = "1", "hello", "2"
+f = try_(int)
+
+ys = zipflatl(f)(xs)
+print(tuple(ys)) # ((1, '1'), (2, '2'))
+```
+
+### *zipflatr*
+
+*zipflatr* is a function which takes a function *Callable[[X], Maybe[Y]]*, and returns some function which takes *Iterable[X]* and returns *Iterable[Tuple[Y, X]]* with only those elements from *Iterable[X]* that are mapped to non-empty *Maybe[Y]* by the function *Callable[[X], Maybe[Y]]*.
+
+```python
+xs = "1", "hello", "2"
+f = try_(int)
+
+ys = zipflatr(f)(xs)
+print(tuple(ys)) # (('1', 1), ('2', 2))
+```
+
+### *zipif*
+
+*zipif* is a function which allows you to zip *Iterable[X]* elements with *Iterable[Y]* elements that match a predicate *Callable[[X, Y], bool]*, using a binary function *Callable[[X, Y], Z]*, into *Iterable[Z]*.
+
+When a pair *x* and *y* do not match the predicate, a function *Callable[[X], Z]* is applied to *x* and its result is yielded. Also, in the next iteration only the first element *x* of the pair will be substituted with it's successor *x'* and *y* will remain unchanged (so that the predicate will get *x'* and *y*).
+
+```python
+participants = (
+ ("Alex", 160.0),
+ ("Sam", 0.0),
+ ("Kate", 150.0),
+ ("John", 155.0),
+ ("Fred", 35.0)
+)
+name = fst
+balance = snd
+
+tickets = (
+ (1, 160),
+ (2, 150),
+ (3, 300)
+)
+ticket_id = fst
+price = snd
+
+sell_tickets = zipif(
+ lambda user, ticket: balance(user) >= price(ticket),
+ lambda user, ticket: (name(user), balance(user) - price(ticket), (ticket,)),
+ lambda user: (*user, ())
+)
+
+for x in sell_tickets(tickets)(participants):
+ print(x)
+
+# ('Alex', 0.0, ((1, 160),))
+# ('Sam', 0.0, ())
+# ('Kate', 0.0, ((2, 150),))
+# ('John', 155.0, ())
+# ('Fred', 35.0, ())
+```
+
+### *zipl*
+
+*zipl* is simply:
+
+```python
+def zipl(xs: Iterable[X]) -> Callable[[Iterable[Y]], Iterable[Tuple[X, Y]]]:
+ return lambda ys: zip(xs, ys)
+```
+
+Example of *zipl* usage:
+
+```python
+xs = "A", "B", "C"
+Pipeline(xs) // zipl(count(1)) * star(flip(repeat)) | print
+
+# A
+# B
+# B
+# C
+# C
+# C
+```
+
+### *zipmapl*
+
+*zipmapl* is simply:
+
+```python
+def zipmapl(f: Callable[[X], Y]) -> Callable[[Iterable[X]], Iterable[Tuple[Y, X]]]:
+ return lambda xs: map(lambda x: (f(x), x), xs)
+```
+
+Example of *zipmapl* usage:
+
+```python
+xs = range(ord("a"), ord("z") + 1)
+upper_to_lower = Pipeline(xs) / chr // zipmapl(str.upper) >> dict
+
+Pipeline(upper_to_lower.items()) // take(5) | print
+
+# ('A', 'a')
+# ('B', 'b')
+# ('C', 'c')
+# ('D', 'd')
+# ('E', 'e')
+```
+
+### *zipmapr*
+
+*zipmapr* is simply:
+
+```python
+def zipmapr(f: Callable[[X], Y]) -> Callable[[Iterable[X]], Iterable[Tuple[X, Y]]]:
+ return lambda xs: map(lambda x: (x, f(x)), xs)
+```
+
+Example of *zipmapr* usage:
+
+```python
+xs = range(ord("a"), ord("z") + 1)
+upper_to_lower = Pipeline(xs) / chr // zipmapr(str.upper) >> dict
+
+Pipeline(upper_to_lower.items()) // take(5) | print
+
+# ('a', 'A')
+# ('b', 'B')
+# ('c', 'C')
+# ('d', 'D')
+# ('e', 'E')
+```
+
+### *zipr*
+
+*zipr* is simply:
+
+```python
+def zipr(ys: Iterable[Y]) -> Callable[[Iterable[X]], Iterable[Tuple[X, Y]]]:
+ return lambda xs: zip(xs, ys)
+```
+
+Example of *zipl* usage:
+
+```python
+xys = {"A": 2.5, "B": 3.14}
+Pipeline(xys.items()) // zipr(count(1)) / flattenl | print
+
+# ('A', 2.5, 1)
+# ('B', 3.14, 2)
+```
+
+
+%package -n python3-nonion
+Summary: Python Functional Programming for Humans.
+Provides: python-nonion
+BuildRequires: python3-devel
+BuildRequires: python3-setuptools
+BuildRequires: python3-pip
+%description -n python3-nonion
+# NOnion
+
+NOnion is a Python package that provides tools for Functional Programming. One of its aims is to eliminate nested function calls such as **z(g(f(x)))** which remind an __onion__.
+
+# Installing
+
+```bash
+pip install nonion
+```
+
+# Tutorial
+
+NOnion contains a set of functions and types that __might__ simplify your workflow with Functional Programming in Python. Those tools are designed (but not limited) to work with *Function* and *Pipeline* wrappers.
+
+* *Function* - a wrapper of **any** Python *Callable*,
+* *Pipeline* - a wrapper of **any** Python *Iterable*.
+
+It is important to understand that *NOnion* provides tools used for FP in context of Python. Because it is impossible to fully implement some constructs from FP languages in Python, *NOnion* provides tools that resemble some of those constructs.
+
+## *Function*
+
+In order to create a *Function*, you simply pass any *Callable*:
+
+```python
+f = Function(lambda x: x + 1)
+f(5) # 6
+```
+
+You can also create an identity *Function*:
+
+```python
+g = Function()
+```
+
+Notice, that a *Function* takes exactly single value and returns exactly single value.
+
+### *compose*
+
+A ``Function composition" defined as $( f \circ g )(x) = f(g(x))$ could be done in the following way:
+
+```python
+z = f @ g
+
+# alternatively
+
+z = f.compose(g)
+```
+
+You can also use *compose* several times:
+
+```python
+z = f @ g @ f
+```
+
+Instead of wrapping each *Callable* with a *Function*, you can wrap only __first__ *Callable* and use *compose* on the rest.
+
+```python
+def f(x):
+ return x + 1
+
+g = Function() @ (lambda x: x * 2) @ f
+g(5) # 12
+```
+
+### *then*
+
+Function composition sometimes might be hard to read, because you have to read it from right-to-left.
+In order to achieve better readability, you can use *then*.
+
+```python
+g = Function() / (lambda x: x * 2) / f
+g(5) # 11
+
+# alternatively
+
+g = Function().then(lambda x: x * 2).then(f)
+g(5) # 11
+```
+
+### *fanout*
+
+If you need to pass an argument to two functions, you can use *fanout*:
+
+```python
+g = Function() / (lambda x: x + 1) & (lambda x: x * 2)
+g(5) # (6, 10)
+
+mean = (Function() / sum & len) / star(op.truediv)
+mean([1, 2, 3]) # 2.0
+
+# alternatively
+
+g = Function().then(lambda x: x + 1).fanout(lambda x: x * 2)
+g(5) # (6, 10)
+
+mean = (Function().then(sum).fanout(len)).then(star(op.truediv))
+mean([1, 2, 3]) # 2.0
+```
+
+### *split*
+
+If you need to apply first value of a pair to a first function and a second value of the pair to a second function, you can use *split*:
+
+```python
+g = Function() / (lambda x: x + 1) ^ (lambda x: x * 2)
+g((2, 3)) # (3, 6)
+
+teams = {"team a": ["member 1", "member 2"], "team b": ["member 3"]}
+f = Function() / str.capitalize ^ len
+for t in teams.items():
+ print(f(t))
+
+# ('Team a', 2)
+# ('Team b', 1)
+
+# alternatively
+
+g = Function().then(lambda x: x + 1).split(lambda x: x * 2)
+g((2, 3)) # (3, 6)
+
+f = Function().then(str.capitalize).split(len)
+for t in teams.items():
+ print(f(t))
+
+# ('Team a', 2)
+# ('Team b', 1)
+```
+
+### *call*
+
+Sometimes you want to call a function ``inline'' after several compositions. In this case, you might use:
+
+```python
+(Function() / (lambda x: x * 2) / f)(5) # 11
+```
+
+But it might be hard to read. Especially, when you mostly pass lambdas. A better way to call a function is by using:
+
+```python
+Function() / (lambda x: x * 2) / f | 5 # 11
+```
+
+### *star* (function)
+
+Suppose, that you defined a function with multiple arguments such as:
+
+```python
+def f(x, y):
+ return x + y * x
+```
+
+And you want to wrap that function using Function. In this case, you have to use *star*.
+
+```python
+Function() @ star(f) | (1, 2) # 3
+```
+
+*star* simply passes arguments to a function using Python *\** (star) operator.
+
+### *unstar* (function)
+
+*unstar* is the opposite function to *star*:
+
+```python
+names = unstar(", ".join)("Haskell Curry", "John Smith", "George Sand")
+print(names) # Haskell Curry, John Smith, George Sand
+```
+
+### *foreach*
+
+You can also call a function for each value in some *Iterable* in the following way:
+
+```python
+ys = Function() / (lambda x: x * 2) / (lambda x: x + 1) * range(5)
+
+for y in ys:
+ print(y)
+
+# 1
+# 3
+# 5
+# 7
+# 9
+#
+```
+
+## *Pipeline*
+
+In order to create a *Pipeline*, you simply pass any *Iterable*:
+
+```python
+xs = Pipeline(range(5))
+
+# notation abuse, do not use that:
+
+xs = Function() / Pipeline | range(5)
+```
+
+You can also create an empty *Pipeline*:
+
+```python
+xs = Pipeline()
+```
+
+Under the hood *Pipeline* is simply uses *iter* on a passed *Iterable*. It means, that if you will pass an *Iterable*, that could be exhausted, you iterate over *Pipeline* only once.
+
+```python
+xs = Pipeline(range(2))
+
+for x in xs:
+ print(x)
+
+# 1
+# 2
+#
+
+# perfectly fine, because range(x) returns a special object
+for x in xs:
+ print(x)
+
+# 1
+# 2
+#
+
+xs = Pipeline(x for x in range(2))
+
+for x in xs:
+ print(x)
+
+# 1
+# 2
+#
+
+# xs already exhausted
+for x in xs:
+ print(x)
+```
+
+### *map*
+
+*map* allows you to call a *Callable*, which takes a single value and returns a single value, on each value of the *Pipeline*.
+
+```python
+ys = Pipeline(range(3)) / (lambda x: x + 1) / (lambda x: (x, x + 1)) / star(lambda x, y: x + y * x)
+
+for y in ys:
+ print(y)
+
+# 3
+# 8
+# 15
+#
+
+# alternatively
+
+ys = Pipeline(range(3)).map(lambda x: x + 1).map(lambda x: (x, x + 1)).map(star(lambda x, y: x + y * x))
+```
+
+### *filter*
+
+*filter* allows you to filter *Pipeline* values.
+
+```python
+ys = Pipeline(range(3)) % (lambda x: x > 1)
+
+for y in ys:
+ print(y)
+
+# 2
+#
+
+# alternatively
+
+ys = Pipeline(range(3)).filter(lambda x: x > 1)
+```
+
+### *flatmap*
+
+*flatmap* allows you to call a *Callable*, which takes a single value and returns an *Iterable*, on each value of the *Pipeline* and flatten results into single *Pipeline*.
+
+```python
+ys = Pipeline(range(2)) / (lambda x: x + 1) * (lambda x: (x, x + 1))
+
+for y in ys:
+ print(y)
+
+# 1
+# 2
+# 2
+# 3
+#
+
+# alternatively
+
+ys = Pipeline(range(2)).map(lambda x: x + 1).flatmap(lambda x: (x, x + 1))
+```
+
+### *apply*
+
+*apply* allows you to call a *Callable*, which takes an *Iterable* and returns an *Iterable*, on whole *Pipeline*.
+
+```python
+ys = Pipeline(range(2)) / (lambda x: x + 1) // tuple # internally Pipeline now has a tuple
+
+for y in ys:
+ print(y)
+
+# 1
+# 2
+#
+
+# now multiple itertations is possible
+for y in ys:
+ print(y)
+
+# 1
+# 2
+#
+
+# alternatively
+
+ys = Pipeline(range(2)).map(lambda x: x + 1).apply(tuple)
+```
+
+### *collect*
+
+*collect* allows you to call a *Callable*, which takes an *Iterable* and returns any single value, on whole *Pipeline*. The difference between *apply* and *collect* is that *collect* returns the result of a function instead of wrapping it with *Pipeline*.
+
+```python
+ys = Pipeline(range(2)) / (lambda x: x + 1) >> tuple
+print(ys)
+
+# (1, 2)
+#
+
+# alternatively
+
+ys = Pipeline(range(2)).map(lambda x: x + 1).collect(tuple)
+```
+
+You can also combine *collect* with any function which takes an *Iterator*:
+
+```python
+ys = Pipeline(range(2)) / (lambda x: x + 1) >> next_
+print(ys) # (1,)
+
+ys = Pipeline(range(2)) % (lambda x: x == 5) >> next_
+print(ys) # ()
+
+ys = Pipeline(range(5)) >> shift(islice, 2)
+
+for y in ys:
+ print(y)
+
+# 0
+# 1
+
+# alternatively you can use apply
+
+ys = Pipeline(range(5)) // shift(islice, 2) | print
+
+# 0
+# 1
+```
+
+### *foreach*
+
+*foreach* allows you to call a *Callable*, which takes a single value, on each value of the *Pipeline*.
+
+```python
+Pipeline(range(2)) / (lambda x: x + 1) | print
+
+# 1
+# 2
+#
+
+# alternatively
+
+Pipeline(range(2)).map(lambda x: x + 1).foreach(print)
+```
+
+## *groupon*
+
+*groupon* is a function which takes a function *Callable[[X], Y]*, and returns some function which takes *Iterable[X]* and returns *Iterable[X]* grouped on *Callable[[X], Y]* function. The *groupon* function uses Python *groupby* function under the hood. *groupon* adds a grouping key using passed *Callable[[X], Y]* function and sorts values by that key before applying *groupby*.
+
+```python
+xs = -3, 1, 0, -1, 5
+
+(
+ Pipeline(xs)
+ // groupon(lambda x: x > 0)
+ / value(tuple)
+ | print
+)
+
+# (False, (-3, 0, -1))
+# (True, (1, 5))
+```
+
+## *nonion.tools*
+
+### *Either*
+
+*Either* is a type alias. *Either* is defined as follows:
+
+```python
+Either = Tuple[Maybe[X], Maybe[Y]]
+```
+
+*Either* can be used when you need to return either left (bad) value or a right (good) value:
+
+```python
+def readline(path: str) -> Either[str, str]:
+ h: Maybe[IOBase] = try_(open)(path)
+
+ if not h:
+ return (("error occurred during open",), ())
+
+ h, *_ = h
+ line = h.readline()
+ h.close()
+
+ return ((), line)
+
+error, line = readline("requirements.txt")
+
+if line:
+ print(*line)
+else:
+ print(*error)
+```
+
+Because *Either* is simply a type alias, it does not checks whether only left or only right value is passed.
+
+### *Maybe*
+
+*Maybe* is a type alias. *Maybe* resembles Haskell's *Maybe* in Python. *Maybe* is defined as follows:
+
+```python
+Maybe = Union[Tuple[X], Tuple[()]]
+```
+
+As we can see *Maybe* is simply some *tuple* that might contain a single value or be an empty *tuple*.
+It means that in order to initialize an *Maybe* you can simply write:
+
+```python
+x = () # empty Maybe
+y = (3,) # Maybe with value 3
+```
+
+You can easily check whether an *Maybe* is empty:
+
+```python
+def f(x: int) -> Maybe[int]:
+ return (x,) if x < 3 else ()
+
+x: Maybe[int] = f(5)
+
+if not x:
+ print("Maybe is empty") # Maybe is empty
+```
+
+You can also provide an alternative value if *Maybe* is empty and immediately try to unwrap the *Maybe*:
+
+```python
+x: Maybe[int] = f(5)
+y, *_ = x or (42,)
+
+print(y) # 42
+```
+
+```python
+# alternatively
+
+x: Maybe[int] = f(1)
+z = x or (42,)
+
+# notice: if you pass an empty *z to a single argument function, you will get an error
+print(*z) # 1
+```
+
+Because *Maybe* is simply a *tuple* under the hood, you can apply any Python function (that operates on *tuple*) to an instance of an *Maybe*.
+
+### *as_catch*
+
+*as_catch* is simply:
+
+```python
+@curry
+def as_catch(default: Callable[[X], Y], xys: Iterable[Tuple[X, Y]]) -> Callable[[X], Y]:
+ return catch(as_match(xys), default=default)
+```
+
+Example of *as_catch* usage:
+
+```python
+successor: Callable[[int], int] = Pipeline(range(10)) // zipmapr(lambda x: x + 1) >> as_catch(lambda _: -1)
+print(successor(1)) # 2
+print(successor(100)) # -1
+```
+
+### *as_match*
+
+*as_match* is simply:
+
+```python
+def as_match(xys: Iterable[Tuple[X, Y]]) -> Callable[[X], Maybe[Y]]:
+ x_to_y = dict(xys)
+
+ def lookup(x: X) -> Maybe[Y]:
+ return (x_to_y[x],) if x in x_to_y else ()
+
+ return lookup
+```
+
+Example of *as_match* usage:
+
+```python
+successor: Callable[[int], Maybe[int]] = Pipeline(range(10)) // zipmapr(lambda x: x + 1) >> as_match
+print(successor(1)) # (2,)
+print(successor(100)) # ()
+```
+
+### *between*
+
+*between* is simply:
+
+```python
+def between(low: float, high: float) -> Callable[[float], bool]:
+ return lambda x: low <= x and x <= high
+```
+
+Example of *between* usage:
+
+```python
+ys = filter(between(3, 5), range(10))
+print(tuple(ys)) # (3, 4, 5)
+```
+
+### *both*
+
+*both* is a function that takes a function *Callable[[X], Y]* and returns a function *Callable[[Tuple[X, X]], Tuple[Y, Y]]*. The returned function takes a pair and applies *Callable[[X], Y]* to both values.
+
+```python
+both(lambda x: x + 1)((1, 2)) # (2, 3)
+```
+
+### *cache*
+
+*cache* is a decorator which returns a function that always returns a value that was returned in the first call.
+
+```python
+def f(x: int) -> int:
+ return x + 5
+
+g = cache(f)
+print(g(5)) # 10
+print(g()) # 10
+print(g("abc", 1, {})) # 10
+
+h = cache(f)
+print(h(7)) # 12
+```
+
+### *catch*
+
+*catch* is a function that resembles pattern-matching in Python. It takes some functions `*fs: Callable[..., Maybe[Y]]` with some catch-all function `default: Callable[..., Y]` and returns a function `Callable[..., Y]` which executes `fs` functions one by one until some function will return non-empty `Maybe[Y]`. If none of those functions will return a non-empty `Maybe[Y]`, the result of `default` function is returned.
+
+```python
+# let's say that we want to parse age ranges that we have in our data:
+age_ranges = (
+ "10-20",
+ "20-30",
+ "30+",
+ "60+",
+ "invalid input"
+)
+
+# we consider 30+ to be a valid range <30, 100)
+
+def parse_range(x: str) -> Tuple[int, int]:
+ raw = x.split("-")
+ low, high, *_ = map(int, raw)
+
+ return low, high
+
+def parse_unbounded_range(x: str) -> Tuple[int, int]:
+ raw, *_ = x.split("+")
+ return int(raw), 100
+
+# we will use <18, 100) as our default range
+parse = catch(
+ try_(parse_range),
+ try_(parse_unbounded_range),
+ default=lambda _: (18, 100)
+)
+
+for x in age_ranges:
+ print(parse(x))
+
+# (10, 20)
+# (20, 30)
+# (30, 100)
+# (60, 100)
+# (18, 100)
+```
+
+### *compose*
+
+*compose* is an implementation of a ``Function composition" defined as $( f \circ g )(x) = f(g(x))$.
+
+```python
+xs = "a", "ab", "c"
+yxs = enumerate(xs)
+
+p: Callable[[Tuple[int, str]], bool] = compose(lambda x: x.startswith("a"), snd)
+filtered: Iterable[Tuple[int, str]] = filter(p, yxs)
+
+ys = map(fst, filtered)
+print(tuple(ys)) # (0, 1)
+```
+
+### *cons*
+
+*cons* allows you to prepend value of type *X* to an *Iterable[X]*.
+
+```python
+print(tuple(cons(1)((2, 3, 4, 5)))) # (1, 2, 3, 4, 5)
+```
+
+### *const*
+
+*const* is a function that takes a value of type *X* and returns a function of type *Callable[[Y], X]*. The returned function will ignore its argument and will return the value that was passed to *const*.
+
+```python
+print(const(1)("abc")) # 1
+```
+
+### *curry*
+
+*curry* is simply:
+
+```python
+def curry(f: Callable[..., Y]) -> Callable[..., Y]:
+ return lambda *args, **kwargs: partial(f, *args, **kwargs)
+```
+
+### *cycle*
+
+*cycle* is a function which takes *Tuple[X, ...]* and returns *Iterable[X]*. *Iterable[X]* is created by repeatedly yielding elements from passed *Tuple[X, ...]*.
+
+```python
+xs = take(10)(cycle([1, 2, 3]))
+print(tuple(xs)) # (1, 2, 3, 1, 2, 3, 1, 2, 3, 1)
+```
+
+### *drop*
+
+*drop* is simply:
+
+```python
+def drop(n: int) -> Callable[[Iterable[X]], Iterable[X]]:
+ return (lambda xs: islice(xs, n, None)) if n > 0 else (lambda _: ())
+```
+
+Example of *drop* usage:
+
+```python
+xs = drop(1)(range(3))
+print(tuple(xs)) # (1, 2)
+
+xs = islice(range(3), 1, None)
+print(tuple(xs)) # (1, 2)
+```
+
+### *either*
+
+*either* is a function that takes a function of type *Callable[[X], Z]* and a function of type *Callable[[Y], Z]* and returns a function of type *Callable[[Either[X, Y]], Z]*. The returned function takes *Either[X, Y]*. If the *Either* contains left value, the value will be applied to *Callable[[X], Z]*. If the *Either* contains right value, the value will be applied to *Callable[[Y], Z]*. The result of application is returned.
+
+```python
+f = either(lambda x: f"Error: {x}", lambda y: f"OK: {y}")
+print(f((("unable to parse",), ()))) # Error: unable to parse
+print(f(((), (1,)))) # OK: 1
+```
+
+### *either_to_maybe*
+
+*either_to_maybe* is an alias for *snd*.
+
+### *except_*
+
+*except_* is a decorator which returns a function that returns *Either* with some value or an *Exception* that was raised.
+
+```python
+f = except_(next)
+xs = iter(range(2))
+
+print(f(xs)) # ((), (0,))
+print(f(xs)) # ((), (1,))
+print(f(xs)) # ((StopIteration(),), ())
+```
+
+### *fail*
+
+*fail* is a function which takes a function *Callable[[Exception], Y]* and returns a decorator which takes a function *Callable[..., Y]* and returns *Callable[..., Y]*. The function returned by the decorator uses passed *Callable[[Exception], Y]* to handle possible errors produced by a decorated function. If no errors produced, *Callable[[Exception], Y]* will not be executed and the result of the decorated function will be returned.
+
+```python
+# Let's say that you want to write is_repeated function
+# which tells you whether you have a collection consisting
+# only from the single value.
+
+# The simplest function you could think of might look like this:
+
+def is_repeated(xs: Iterable[X]) -> bool:
+ x, *rest = xs
+ return all(x == y for y in rest)
+
+# It works on collections that have at least one value:
+
+print(is_repeated((1, 1))) # True
+print(is_repeated((1, 2, 3))) # False
+
+# but when you have an empty collection, this function will result
+# in an error:
+
+print(is_repeated(()))
+# ValueError: not enough values to unpack (expected at least 1, got 0)
+
+# In order to handle this case, you can rewrite this function in a
+# following manner:
+
+def is_repeated(xs: Iterable[X]) -> bool:
+ xs = iter(xs)
+ wrapped_x = next_(xs)
+
+ if wrapped_x:
+ x, *_ = wrapped_x
+ return all(x == y for y in xs)
+ else: return True
+
+# And it would work:
+
+print(is_repeated((1, 1))) # True
+print(is_repeated((1, 2, 3))) # False
+print(is_repeated(())) # True
+
+# You might also use a *fail* function which will surround your
+# function with try-except clause, to deal with empty collection.
+
+@fail(lambda _: True)
+def is_repeated(xs: Iterable[X]) -> bool:
+ x, *rest = xs
+ return all(x == y for y in rest)
+
+# In case when error is raised by is_repeated, the
+# lambda _: True
+# function will be executed. The raised error will be passed to
+# that function.
+
+def g(e: Exception) -> bool:
+ print(e)
+ return True
+
+@fail(g)
+def is_repeated(xs: Iterable[X]) -> bool:
+ x, *rest = xs
+ return all(x == y for y in rest)
+
+print(is_repeated((1,))) # True
+print(is_repeated(()))
+# not enough values to unpack (expected at least 1, got 0)
+# True
+```
+
+### *find*
+
+*find* is a function which takes a predicate and returns a function which takes some *Iterable* and returns an *Maybe* with value that matches the predicate if such value exists:
+
+```python
+x: Maybe[int] = find(lambda x: x == 3)(range(5))
+print(x) # (3,)
+
+x: Maybe[int] = find(lambda x: x == -1)(range(5))
+print(x) # ()
+```
+
+### *findindex*
+
+*findindex* is a function that works like *find*, but instead of returning a function which returns a value in *Iterable* that matches some predicate, it returns a function which returns an index of that value in *Iterable*.
+
+```python
+x: Maybe[int] = findindex(lambda x: x == 8)(range(5, 10))
+print(x) # (3,)
+
+x: Maybe[int] = findindex(lambda x: x == -1)(range(5, 10))
+print(x) # ()
+```
+
+### *finds*
+
+*finds* is a function which takes an *Iterable[Callable[[X], bool]]* of predicates and returns a function which takes some *Iterable[X]* and returns an *Iterable[Maybe[X]]*. *finds* iterates over each predicate and searches for a matching value for that predicate in the passed *Iterable[X]*. *finds* will store checked *Iterable[X]* values in a buffer, so that the buffer will be checked at first and (if needed) the remaining *Iterable[X]* will be checked at last.
+
+```python
+fs = (lambda x: x == 2), (lambda x: x == 4), (lambda x: x == 1), (lambda x: x == -1)
+ys: Iterable[Maybe[int]] = finds(fs)(range(5))
+
+for y in ys:
+ print(y)
+
+# (2,)
+# (4,)
+# (1,)
+# ()
+```
+
+### *flattenl*
+
+*flattenl* is a function which takes a *Tuple* which contains another *Tuple* on the beginning and flattens that inner *Tuple* inside of outer *Tuple*.
+
+```python
+xys = {"A": 2.5, "B": 3.14}
+Pipeline(xys.items()) // zipr(count(1)) / flattenl | print
+
+# ('A', 2.5, 1)
+# ('B', 3.14, 2)
+```
+
+### *flattenr*
+
+*flattenr* is a function which takes a *Tuple* which contains another *Tuple* on the end and flattens that inner *Tuple* inside of outer *Tuple*.
+
+```python
+xys = {"A": 2.5, "B": 3.14}
+Pipeline(xys.items()) // zipl(count(1)) / flattenr | print
+
+# (1, 'A', 2.5)
+# (2, 'B', 3.14)
+```
+
+### *flip*
+
+*flip* is simply:
+
+```python
+def flip(f: Callable[[Y, X], Z]) -> Callable[[X, Y], Z]:
+ return lambda x, y: f(y, x)
+```
+
+Example of *flip* usage:
+
+```python
+xs = "A", "B", "C"
+Pipeline(enumerate(xs)) / key(lambda x: x + 1) * star(flip(repeat)) | print
+
+# A
+# B
+# B
+# C
+# C
+# C
+```
+
+### *foldl*
+
+*foldl* is a function which takes a binary function *Callable[[Y, X], Y]* and some accumulator *Y* and returns a function which takes *Iterable[X]* and returns *Y*. This function allows you to fold *Iterable[X]* from __left__ using passed binary function. The accumulator is being passed as the __first__ argument of the binary function.
+
+Example of *foldl* usage:
+
+```python
+xs = range(ord("A"), ord("Z") + 1)
+alphabet = Pipeline(xs) / chr >> foldl(operator.add, "")
+
+print(alphabet)
+
+# ABCDEFGHIJKLMNOPQRSTUVWXYZ
+```
+
+### *foldl1*
+
+*foldl1* is a similar function to *foldl*. The difference between *foldl1* and *foldl* is that *foldl1* takes *Callable[[X, X], X]*, uses *Iterable[X]* __first__ element as the accumulator and returns *X*. *foldl1* will raise an error if the supplied *Iterable[X]* is empty.
+
+### *foldr*
+
+*foldr* is a function which takes a binary function *Callable[[X, Y], Y]* and some accumulator *Y* and returns a function which takes *Iterable[X]* and returns *Y*. This function allows you to fold *Iterable[X]* from __right__ using passed binary function. The accumulator is being passed as the __last__ argument of the binary function.
+
+Example of *foldr* usage:
+
+```python
+xs = range(ord("A"), ord("Z") + 1)
+reversed_alphabet = (
+ Pipeline(xs)
+ / chr
+ // foldr(lambda x, acc: acc + [x], [])
+ >> foldl(operator.add, "")
+)
+
+print(reversed_alphabet)
+
+# ZYXWVUTSRQPONMLKJIHGFEDCBA
+```
+
+### *foldr1*
+
+*foldr1* is a similar function to *foldr*. The difference between *foldr1* and *foldr* is that *foldr1* takes *Callable[[X, X], X]*, uses *Iterable[X]* __last__ element as the accumulator and returns *X*. *foldr1* will raise an error if the supplied *Iterable[X]* is empty. Under the hood *foldr1* will use *tuple* on passed *Iterable[X]* in order to extract the accumulator.
+
+### *fst*
+
+*fst* is simply:
+
+```python
+def fst(xy: Tuple[X, Y]) -> X:
+ return xy[0]
+```
+
+### *group*
+
+*group* is a function which takes *Iterable[X]* and returns *Iterable[Tuple[X, ...]]*. This function groups passed elements by equality comparison `==`.
+
+```python
+xs = 1, 1, 2, 2, 2, 3, 1, 1, 1
+print(tuple(group(xs))) # ((1, 1), (2, 2, 2), (3,), (1, 1, 1))
+```
+
+### *groupby*
+
+*groupby* is a function which takes an equality comparison function *Callable[[X, X], bool]* and returns a function *Callable[[Iterable[X]], Iterable[Tuple[X, ...]]]* which groups passed elements by the equality comparison function.
+
+```python
+people = (
+ ("Alex", 23),
+ ("John", 23),
+ ("Sam", 27),
+ ("Kate", 27),
+ ("Fred", 23),
+)
+
+grouped = groupby(lambda x, y: snd(x) == snd(y))(people)
+print(tuple(grouped))
+# ((('Alex', 23), ('John', 23)), (('Sam', 27), ('Kate', 27)), (('Fred', 23),))
+
+# or you can use *on* function:
+
+grouped = groupby(on(operator.eq, snd))(people)
+print(tuple(grouped))
+# ((('Alex', 23), ('John', 23)), (('Sam', 27), ('Kate', 27)), (('Fred', 23),))
+```
+
+### *in_*
+
+*in_* is simply:
+
+```python
+def in_(xs: Tuple[X, ...]) -> Callable[[X], bool]:
+ return lambda x: x in xs
+```
+
+### *key*
+
+*key* is simply:
+
+```python
+def key(f: Callable[[X], Z]) -> Callable[[Tuple[X, Y]], Tuple[Z, Y]]:
+ g: Callable[[Tuple[X, Y]], Z] = compose(f, fst)
+ return lambda xy: (g(xy), snd(xy))
+```
+
+Example of *key* usage:
+
+```python
+xys = {"A": [1, 2, 3], "B": [3, 4]}
+zys = map(key(str.casefold), xys.items())
+
+for zy in zys:
+ print(zy)
+
+# ('a', [1, 2, 3])
+# ('b', [3, 4])
+```
+
+### *length*
+
+*length* is a function which takes an *Iterable* and returns number of elements in that *Iterable*. *length* exhausts the *Iterable*.
+
+```python
+xs = 1, 2, 3
+print(len(xs)) # 3
+
+# len(iter(xs)) will raise an error
+print(length(iter(xs))) # 3
+```
+
+### *lift*
+
+*lift* is simply:
+
+```python
+lift = curry(map)
+```
+
+### *match_*
+
+*match_* is a function that resembles pattern-matching in Python. It takes some functions `*fs: Callable[..., Maybe[Y]]` and returns a function `Callable[..., Maybe[Y]]` which executes `fs` functions one by one until some function will return non-empty `Maybe[Y]`. If none of those functions will return a non-empty `Maybe[Y]`, an empty `Maybe[Y]` (i.e. `()`) is returned.
+
+```python
+# let's say that we want to parse age ranges that we have in our data:
+age_ranges = (
+ "10-20",
+ "20-30",
+ "30+",
+ "60+",
+ "invalid input"
+)
+
+# we consider 30+ to be a valid range <30, 100)
+
+def parse_range(x: str) -> Tuple[int, int]:
+ raw = x.split("-")
+ low, high, *_ = map(int, raw)
+
+ return low, high
+
+def parse_unbounded_range(x: str) -> Tuple[int, int]:
+ raw, *_ = x.split("+")
+ return int(raw), 100
+
+parse = match_(
+ try_(parse_range),
+ try_(parse_unbounded_range)
+)
+
+for x in age_ranges:
+ print(parse(x))
+
+# ((10, 20),)
+# ((20, 30),)
+# ((30, 100),)
+# ((60, 100),)
+# ()
+```
+
+### *maybe*
+
+*maybe* is a function that takes a function of type *Callable[[], Y]* and a function of type *Callable[[X], Y]* and returns a function of type *Callable[[Maybe[X]], Y]*. The returned function takes *Maybe[X]*. If the *Maybe* contains value, the value will be applied to *Callable[[X], Y]* and a result of application is returned. If the *Maybe* contains no value, the *Callable[[], Y]* is called and a result is returned.
+
+```python
+f = maybe(lambda: "Error", lambda x: f"OK: {x}")
+print(f(())) # Error
+print(f((1,))) # OK: 1
+```
+
+### *maybe_to_either*
+
+*maybe_to_either* is a function which allows you to create an *Either[Y, X]* from an *Maybe[X]*. *maybe_to_either* takes a function *Callable[[], Y]* and returns a function *Callable[[Maybe[X]], Either[Y, X]]*.
+
+```python
+raw_numbers = "1\n22\nten\n333".splitlines()
+
+xs = (
+ Pipeline(raw_numbers)
+ / try_(int)
+ / maybe_to_either(lambda: f"Failed to parse.")
+ | print
+)
+
+# ((), (1,))
+# ((), (22,))
+# (('Failed to parse.',), ())
+# ((), (333,))
+```
+
+The difference between using *maybe_to_either* and explicitly creating *Either* using tuples is that *maybe_to_either* will not evaluate the left part if the right part is present. That is why *Callable[[], Y]* is being passed to *maybe_to_either* instead of *Y*.
+
+### *merge*
+
+*merge* is a function which takes two sorted *Iterable[X]* and merges them into single sorted *Iterable[X]*. It uses *lambda x, y: x <= y* comparison function. Use *key* parameter to specify a function *Callable[[X], Y]* to be called on each element prior to making comparisons.
+
+```python
+xs = merge((1, 3, 5), (1, 2, 4))
+print(tuple(xs)) # (1, 1, 2, 3, 4, 5)
+
+xs, ys = [(1, "a"), (3, "d"), (5, "f")], [(1, "b"), (2, "c"), (4, "e")]
+print(tuple((merge(xs, ys, key=fst))))
+# ((1, 'a'), (1, 'b'), (2, 'c'), (3, 'd'), (4, 'e'), (5, 'f'))
+```
+
+### *next_*
+
+*next_* is simply:
+
+```python
+next_: Callable[[Iterator[X]], Maybe[X]] = try_(next)
+```
+
+Example of *next_* usage:
+
+```python
+xs = iter(range(2))
+
+print(next_(xs)) # (0,)
+print(next_(xs)) # (1,)
+print(next_(xs)) # ()
+```
+
+### *not_*
+
+*not_* is a function which takes a predicate and returns negation of that predicate.
+
+```python
+print(not_(lambda x, y: x == y)(1, 5)) # True
+```
+
+### *on*
+
+*on* is simply:
+
+```python
+def on(f: Callable[[Y, Y], Z], g: Callable[[X], Y]) -> Callable[[X, X], Z]:
+ return lambda p, n: f(g(p), g(n))
+```
+
+Example of *on* usage could be found in *groupby* section.
+
+### *padl*
+
+*padl* is a function which allows you to pad some *Iterable* from the __left__ using a filler. This function takes a number *n* and a filler *x*. In case when **exact=False** option is passed, it returns a function which prepends *n - k* fillers to the passed *Iterable*, where *k* is a length of the passed *Iterable*. In case when **exact=True** option is passed, it returns a function which prepends __exactly__ *n* fillers to the passed *Iterable*.
+
+```python
+xs = "".join(padl(5, "x")("abc"))
+print(xs) # xxabc
+
+xs = "".join(padl(5, "x", exact=True)("abc"))
+print(xs) # xxxxxabc
+```
+
+### *padr*
+
+*padr* is a function which allows you to pad some *Iterable* from the __right__ using a filler. This function takes a number *n* and a filler *x*. In case when **exact=False** option is passed, it returns a function which appends *n - k* fillers to the passed *Iterable*, where *k* is a length of the passed *Iterable*. In case when **exact=True** option is passed, it returns a function which appends __exactly__ *n* fillers to the passed *Iterable*.
+
+```python
+xs = "".join(padr(5, "x")("abc"))
+print(xs) # abcxx
+
+xs = "".join(padr(5, "x", exact=True)("abc"))
+print(xs) # abcxxxxx
+```
+
+### *partition*
+
+*partition* is a function which takes a predicate and returns a function *Callable[[Iterable[X]], Tuple[Tuple[X, ...], Tuple[X, ...]]]*. This returned function splits passed elements into those that do match the predicate and the rest. The difference between *span* and *partition* is that *span* stops when it finds the first element that does not match the predicate and *partition* goes until the end.
+
+```python
+xs = 1, 1, 2, 2, 2, 3, 1, 1, 1
+matched, rest = partition(lambda x: x == 1)(xs)
+
+print(matched) # (1, 1, 1, 1, 1)
+print(rest) # (2, 2, 2, 3)
+```
+
+### *peek*
+
+*peek* is a decorator which allows you to apply some function to a passed argument and return back the passed argument instead of a function's result.
+
+```python
+x = peek(print)(5) # 5
+print(x) # 5
+
+xs = (
+ Pipeline(range(3, 0, -1))
+ / peek(print, "Countdown:", file=sys.stderr)
+ >> tuple
+)
+# Countdown: 3
+# Countdown: 2
+# Countdown: 1
+
+print(xs) # (3, 2, 1)
+```
+
+### *pick*
+
+*pick* is a function which takes some aggregate function *Callable[[Tuple[X, ...]], X]* and returns a function *Callable[[Iterable[X]], Iterable[X]]*. This returned function __picks__ all elements from the passed collection *Iterable[X]* which are equal to the value returned by the aggregate function. The equal function might be substituted with any other function of type signature *Callable[[X, X], bool]* by passing a *compare* parameter.
+
+```python
+print(min([1, 2, 1, 1, 3, 1])) # 1
+
+ys = tuple(pick(min)([1, 2, 1, 1, 3, 1]))
+print(ys) # (1, 1, 1, 1)
+
+print(min([]))
+# ValueError: min() arg is an empty sequence
+
+ys = tuple(pick(min)([]))
+print(ys) # ()
+```
+
+### *pickby*
+
+*pickby* is a function which takes a function *Callable[[X], Y]*, an aggregate function *Callable[[Tuple[Y, ...]], Y]* and returns a function *Callable[[Iterable[X]], Iterable[X]]*. This returned function __picks__ all elements from the passed collection *Iterable[X]* which corresponding *Y* values, created by the *Callable[[X], Y]* function, are equal to the value returned by the aggregate function. The equal function might be substituted with any other function of type signature *Callable[[Y, Y], bool]* by passing a *compare* parameter. The function *Callable[[X], Y]* will be used exactly once on the whole collection.
+
+```python
+cars_and_prices = (
+ ("Audi", 25000),
+ ("BMW", 70000),
+ ("Mercedes", 25000),
+)
+
+cheapest_car = min(cars_and_prices, key=snd)
+print(cheapest_car) # ('Audi', 25000)
+
+cheapest_cars = pickby(snd, min)(cars_and_prices)
+print(tuple(cheapest_cars)) # (('Audi', 25000), ('Mercedes', 25000))
+```
+
+### *powerset*
+
+*powerset* is a function which takes a *Tuple[X, ...]* and produces power set of those elements in form of *Iterable[Iterable[X]]*.
+
+```python
+xs = tuple(range(3))
+ps = tuple(map(tuple, powerset(xs)))
+
+print(ps) # ((), (2,), (1,), (1, 2), (0,), (0, 2), (0, 1), (0, 1, 2))
+```
+
+### *replicate*
+
+*replicate* is a function which takes a number *n* and returns a function, which takes some value *x* and repeats *n* times value *x*.
+
+```python
+xs = tuple(replicate(5)("hello"))
+print(xs)
+# ('hello', 'hello', 'hello', 'hello', 'hello')
+```
+
+### *reverse*
+
+*reverse* is a function which takes an *Iterable[X]* and returns *Deque[X]* which contains elements from *Iterable[X]* in reversed order.
+
+```python
+xs = range(ord("A"), ord("Z") + 1)
+
+reversed_alphabet = (
+ Pipeline(xs)
+ / chr
+ // reverse # Python reversed would not work on Iterable
+ >> foldl(operator.add, "")
+)
+
+print(reversed_alphabet)
+
+# ZYXWVUTSRQPONMLKJIHGFEDCBA
+```
+
+### *scanl*
+
+*scanl* is a similar function to *foldl*. The difference between *scanl* and *foldl* is that *scanl* instead of returning a function which takes *Iterable[X]* and returns *Y*, returns a function which takes *Iterable[X]* and returns *Iterable[Y]*. The resulting *Iterable[Y]* contains all accumulators used in *foldl*.
+
+```python
+xs = scanl(operator.mul, 1)((1, 2, 3, 4, 5))
+print(tuple(xs))
+# (1, 1, 2, 6, 24, 120)
+```
+
+### *scanl1*
+
+*scanl1* is a similar function to *foldl1*. The difference between *scanl1* and *foldl1* is that *scanl1* instead of returning a function which takes *Iterable[X]* and returns *X*, returns a function which takes *Iterable[X]* and returns *Iterable[X]*. The resulting *Iterable[X]* contains all accumulators used in *foldl1*.
+
+```python
+xs = scanl1(operator.mul)((1, 2, 3, 4, 5))
+print(tuple(xs))
+# (1, 2, 6, 24, 120)
+```
+
+### *scanr*
+
+*scanr* is a similar function to *foldr*. The difference between *scanr* and *foldr* is that *scanr* instead of returning a function which takes *Iterable[X]* and returns *Y*, returns a function which takes *Iterable[X]* and returns *Deque[Y]*. The resulting *Deque[Y]* contains all accumulators used in *foldr*.
+
+```python
+xs = scanr(operator.mul, 1)((1, 2, 3, 4, 5))
+print(xs)
+# deque([120, 120, 60, 20, 5, 1])
+```
+
+### *scanr1*
+
+*scanr1* is a similar function to *foldr1*. The difference between *scanr1* and *foldr1* is that *scanr1* instead of returning a function which takes *Iterable[X]* and returns *X*, returns a function which takes *Iterable[X]* and returns *Deque[X]*. The resulting *Deque[X]* contains all accumulators used in *foldr1*.
+
+```python
+xs = scanr1(operator.mul)((1, 2, 3, 4, 5))
+print(xs)
+# deque([120, 120, 60, 20, 5])
+```
+
+### *search*
+
+*search* is a function which takes a predicate *Callable[[X], bool]* along with *Iterable[Y]* and returns a function which takes *Iterable[X]* and returns *Iterable[Y]*. This function zips *Iterable[Y]* with *Iterable[X]* and returns those *Y*s for which corresponding *X*s match the predicate.
+
+```python
+xs = search(lambda x: x > 3, count())(range(1, 6))
+print(tuple(xs))
+# (3, 4)
+```
+
+### *shift*
+
+*shift* is a decorator which returns a partially applied function. The difference between Python's *functools.partial* and *shift* is that *shift* will return a function which prepends *\*args* and *\*\*kwargs*:
+
+```python
+def dummy(*args: object, **kwargs: object):
+ print(args)
+ print(kwargs)
+
+partial(dummy, 1, 2, a=1, b="b")(3, 4, c="c")
+print("-" * 10)
+shift(dummy, 1, 2, a=1, b="b")(3, 4, c="c")
+
+# (1, 2, 3, 4)
+# {'a': 1, 'b': 'b', 'c': 'c'}
+# ----------
+# (3, 4, 1, 2)
+# {'c': 'c', 'a': 1, 'b': 'b'}
+```
+
+Example of *shift* usage:
+
+```python
+take_3 = shift(islice, 3)
+xs: Iterable[int] = take_3(range(5))
+
+for x in xs:
+ print(x)
+
+# 0
+# 1
+# 2
+```
+
+### *slide*
+
+*slide* is a function which takes a sliding window length **n** and a **step**, and returns a function which takes an *Iterable* and applies sliding window over it resulting in an *Iterable* of *tuple*s. Each *tuple* has at most length equal to **n**. In case when **exact=True** option is passed, each *tuple* has length equal to **n**. **step** is simply a shift of a sliding window.
+
+```python
+xs: Iterable[Tuple[int, ...]] = slide()(range(10))
+print(tuple(xs))
+# ((0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 7), (7, 8), (8, 9), (9,))
+
+xs: Iterable[Tuple[int, ...]] = slide(n=3, step=2)(range(10))
+print(tuple(xs))
+# ((0, 1, 2), (2, 3, 4), (4, 5, 6), (6, 7, 8), (8, 9))
+
+xs: Iterable[Tuple[int, ...]] = slide(n=3, step=2, exact=True)(range(10))
+print(tuple(xs))
+# ((0, 1, 2), (2, 3, 4), (4, 5, 6), (6, 7, 8))
+
+def is_sorted(xs: Iterable[X], compare: Callable[[X, X], bool] = operator.le) -> bool:
+ return (
+ Pipeline(slide(exact=True)(xs))
+ / star(compare)
+ >> all
+ )
+
+print(is_sorted((1, 2, 5))) # True
+print(is_sorted((1, 2, -5))) # False
+```
+
+### *snd*
+
+*snd* is simply:
+
+```python
+def snd(xy: Tuple[X, Y]) -> Y:
+ return xy[1]
+```
+
+### *span*
+
+*span* is a function which takes a predicate and returns a function *Callable[[Iterable[X]], Tuple[Tuple[X, ...], Iterable[X]]]*. This returned function splits passed elements into those that do match the predicate on the beginning and the rest.
+
+```python
+xs = 1, 1, 2, 2, 2, 3, 1, 1, 1
+matched, rest = span(lambda x: x == 1)(xs)
+
+print(matched) # (1, 1)
+print(tuple(rest)) # (2, 2, 2, 3, 1, 1, 1)
+```
+
+### *splitat*
+
+*splitat* is a function which takes an index *i* and returns a function which splits an *Iterable[X]* into *Tuple[X, ...]* and *Iterable[X]*. The *Tuple[X, ...]* will contain first *i* elements and the *Iterable[X]* will contain the rest.
+
+```python
+xs, rest = splitat(1)(range(5))
+print(xs) # (0,)
+print(tuple(rest)) # (1, 2, 3, 4)
+```
+
+### *strip*
+
+*strip* is a function which takes an *Iterable[X]* and returns an *Iterable[X]* with removed consecutive duplicates. *strip* functions uses only equality comparison `==`.
+
+```python
+xs = 1, 1, 2, 2, 2, 3, 1, 1, 1
+print(tuple(strip(xs))) # (1, 2, 3, 1)
+```
+
+### *stripby*
+
+*stripby* is a function which takes an equality comparison function *Callable[[X, X], bool]* and returns a function *Callable[[Iterable[X]], Iterable[X]]* which removes consecutive duplicates in terms of the equality comparison function.
+
+```python
+people = (
+ ("Alex", 23),
+ ("John", 23),
+ ("Sam", 27),
+ ("Kate", 27),
+ ("Fred", 23),
+)
+
+stripped = stripby(lambda x, y: snd(x) == snd(y))(people)
+print(tuple(stripped))
+# (('Alex', 23), ('Sam', 27), ('Fred', 23))
+
+# or you can use *on* function:
+
+stripped = stripby(on(operator.eq, snd))(people)
+print(tuple(stripped))
+# (('Alex', 23), ('Sam', 27), ('Fred', 23))
+```
+
+### *take*
+
+*take* is simply:
+
+```python
+def take(n: int) -> Callable[[Iterable[X]], Iterable[X]]:
+ return (lambda xs: islice(xs, n)) if n > 0 else (lambda _: ())
+```
+
+Example of *take* usage:
+
+```python
+xs = take(1)(range(3))
+print(tuple(xs)) # (0,)
+
+xs = islice(range(3), 1)
+print(tuple(xs)) # (0,)
+```
+
+### *try_*
+
+*try_* is a decorator which returns a function that returns *Maybe* with some value or an empty *Maybe* if an *Exception* was raised.
+
+```python
+load_json = try_(json.loads)
+
+print(load_json("{}")) # ({},)
+print(load_json("[1, 2, 3]")) # ([1, 2, 3],)
+print(load_json("abc")) # ()
+```
+
+### *unfoldr*
+
+*unfoldr* is a function that takes a function of type *Callable[[X], Maybe[Tuple[Y, X]]]* and returns a function of type *Callable[[X], Iterable[Y]]*. The returned function will repeatedly apply *X* to the function passed to *unfoldr* until it returns value *()*.
+
+```python
+until_3 = unfoldr(lambda acc: ((acc, acc + 1),) if acc < 3 else ())
+print(tuple(until_3(0))) # (0, 1, 2)
+```
+
+### *value*
+
+*value* is simply:
+
+```python
+def value(f: Callable[[Y], Z]) -> Callable[[Tuple[X, Y]], Tuple[X, Z]]:
+ g: Callable[[Tuple[X, Y]], Z] = compose(f, snd)
+ return lambda xy: (fst(xy), g(xy))
+```
+
+Example of *value* usage:
+
+```python
+xys = {"A": [1, 2, 3], "B": [3, 4]}
+xzs = map(value(len), xys.items())
+
+for xz in xzs:
+ print(xz)
+
+# ('A', 3)
+# ('B', 2)
+```
+
+### *where*
+
+*where* is a similar function to *findindex*. The difference between *where* and *findindex* is that *where* returns indices of all elements that match given predicate instead of one. The other difference is that *where* returns a function which takes *Iterable[X]* and returns *Iterable[Y]*, on the other hand *findindex* returns a function which takes *Iterable[X]* and returns *Maybe[int]*.
+
+```python
+xs = where(lambda x: x >= 8)(range(5, 10))
+print(tuple(xs)) # (3, 4)
+
+xs = where(lambda x: x == -1)(range(5, 10))
+print(tuple(xs)) # ()
+```
+
+### *zipflatl*
+
+*zipflatl* is a function which takes a function *Callable[[X], Maybe[Y]]*, and returns some function which takes *Iterable[X]* and returns *Iterable[Tuple[X, Y]]* with only those elements from *Iterable[X]* that are mapped to non-empty *Maybe[Y]* by the function *Callable[[X], Maybe[Y]]*.
+
+```python
+xs = "1", "hello", "2"
+f = try_(int)
+
+ys = zipflatl(f)(xs)
+print(tuple(ys)) # ((1, '1'), (2, '2'))
+```
+
+### *zipflatr*
+
+*zipflatr* is a function which takes a function *Callable[[X], Maybe[Y]]*, and returns some function which takes *Iterable[X]* and returns *Iterable[Tuple[Y, X]]* with only those elements from *Iterable[X]* that are mapped to non-empty *Maybe[Y]* by the function *Callable[[X], Maybe[Y]]*.
+
+```python
+xs = "1", "hello", "2"
+f = try_(int)
+
+ys = zipflatr(f)(xs)
+print(tuple(ys)) # (('1', 1), ('2', 2))
+```
+
+### *zipif*
+
+*zipif* is a function which allows you to zip *Iterable[X]* elements with *Iterable[Y]* elements that match a predicate *Callable[[X, Y], bool]*, using a binary function *Callable[[X, Y], Z]*, into *Iterable[Z]*.
+
+When a pair *x* and *y* do not match the predicate, a function *Callable[[X], Z]* is applied to *x* and its result is yielded. Also, in the next iteration only the first element *x* of the pair will be substituted with it's successor *x'* and *y* will remain unchanged (so that the predicate will get *x'* and *y*).
+
+```python
+participants = (
+ ("Alex", 160.0),
+ ("Sam", 0.0),
+ ("Kate", 150.0),
+ ("John", 155.0),
+ ("Fred", 35.0)
+)
+name = fst
+balance = snd
+
+tickets = (
+ (1, 160),
+ (2, 150),
+ (3, 300)
+)
+ticket_id = fst
+price = snd
+
+sell_tickets = zipif(
+ lambda user, ticket: balance(user) >= price(ticket),
+ lambda user, ticket: (name(user), balance(user) - price(ticket), (ticket,)),
+ lambda user: (*user, ())
+)
+
+for x in sell_tickets(tickets)(participants):
+ print(x)
+
+# ('Alex', 0.0, ((1, 160),))
+# ('Sam', 0.0, ())
+# ('Kate', 0.0, ((2, 150),))
+# ('John', 155.0, ())
+# ('Fred', 35.0, ())
+```
+
+### *zipl*
+
+*zipl* is simply:
+
+```python
+def zipl(xs: Iterable[X]) -> Callable[[Iterable[Y]], Iterable[Tuple[X, Y]]]:
+ return lambda ys: zip(xs, ys)
+```
+
+Example of *zipl* usage:
+
+```python
+xs = "A", "B", "C"
+Pipeline(xs) // zipl(count(1)) * star(flip(repeat)) | print
+
+# A
+# B
+# B
+# C
+# C
+# C
+```
+
+### *zipmapl*
+
+*zipmapl* is simply:
+
+```python
+def zipmapl(f: Callable[[X], Y]) -> Callable[[Iterable[X]], Iterable[Tuple[Y, X]]]:
+ return lambda xs: map(lambda x: (f(x), x), xs)
+```
+
+Example of *zipmapl* usage:
+
+```python
+xs = range(ord("a"), ord("z") + 1)
+upper_to_lower = Pipeline(xs) / chr // zipmapl(str.upper) >> dict
+
+Pipeline(upper_to_lower.items()) // take(5) | print
+
+# ('A', 'a')
+# ('B', 'b')
+# ('C', 'c')
+# ('D', 'd')
+# ('E', 'e')
+```
+
+### *zipmapr*
+
+*zipmapr* is simply:
+
+```python
+def zipmapr(f: Callable[[X], Y]) -> Callable[[Iterable[X]], Iterable[Tuple[X, Y]]]:
+ return lambda xs: map(lambda x: (x, f(x)), xs)
+```
+
+Example of *zipmapr* usage:
+
+```python
+xs = range(ord("a"), ord("z") + 1)
+upper_to_lower = Pipeline(xs) / chr // zipmapr(str.upper) >> dict
+
+Pipeline(upper_to_lower.items()) // take(5) | print
+
+# ('a', 'A')
+# ('b', 'B')
+# ('c', 'C')
+# ('d', 'D')
+# ('e', 'E')
+```
+
+### *zipr*
+
+*zipr* is simply:
+
+```python
+def zipr(ys: Iterable[Y]) -> Callable[[Iterable[X]], Iterable[Tuple[X, Y]]]:
+ return lambda xs: zip(xs, ys)
+```
+
+Example of *zipl* usage:
+
+```python
+xys = {"A": 2.5, "B": 3.14}
+Pipeline(xys.items()) // zipr(count(1)) / flattenl | print
+
+# ('A', 2.5, 1)
+# ('B', 3.14, 2)
+```
+
+
+%package help
+Summary: Development documents and examples for nonion
+Provides: python3-nonion-doc
+%description help
+# NOnion
+
+NOnion is a Python package that provides tools for Functional Programming. One of its aims is to eliminate nested function calls such as **z(g(f(x)))** which remind an __onion__.
+
+# Installing
+
+```bash
+pip install nonion
+```
+
+# Tutorial
+
+NOnion contains a set of functions and types that __might__ simplify your workflow with Functional Programming in Python. Those tools are designed (but not limited) to work with *Function* and *Pipeline* wrappers.
+
+* *Function* - a wrapper of **any** Python *Callable*,
+* *Pipeline* - a wrapper of **any** Python *Iterable*.
+
+It is important to understand that *NOnion* provides tools used for FP in context of Python. Because it is impossible to fully implement some constructs from FP languages in Python, *NOnion* provides tools that resemble some of those constructs.
+
+## *Function*
+
+In order to create a *Function*, you simply pass any *Callable*:
+
+```python
+f = Function(lambda x: x + 1)
+f(5) # 6
+```
+
+You can also create an identity *Function*:
+
+```python
+g = Function()
+```
+
+Notice, that a *Function* takes exactly single value and returns exactly single value.
+
+### *compose*
+
+A ``Function composition" defined as $( f \circ g )(x) = f(g(x))$ could be done in the following way:
+
+```python
+z = f @ g
+
+# alternatively
+
+z = f.compose(g)
+```
+
+You can also use *compose* several times:
+
+```python
+z = f @ g @ f
+```
+
+Instead of wrapping each *Callable* with a *Function*, you can wrap only __first__ *Callable* and use *compose* on the rest.
+
+```python
+def f(x):
+ return x + 1
+
+g = Function() @ (lambda x: x * 2) @ f
+g(5) # 12
+```
+
+### *then*
+
+Function composition sometimes might be hard to read, because you have to read it from right-to-left.
+In order to achieve better readability, you can use *then*.
+
+```python
+g = Function() / (lambda x: x * 2) / f
+g(5) # 11
+
+# alternatively
+
+g = Function().then(lambda x: x * 2).then(f)
+g(5) # 11
+```
+
+### *fanout*
+
+If you need to pass an argument to two functions, you can use *fanout*:
+
+```python
+g = Function() / (lambda x: x + 1) & (lambda x: x * 2)
+g(5) # (6, 10)
+
+mean = (Function() / sum & len) / star(op.truediv)
+mean([1, 2, 3]) # 2.0
+
+# alternatively
+
+g = Function().then(lambda x: x + 1).fanout(lambda x: x * 2)
+g(5) # (6, 10)
+
+mean = (Function().then(sum).fanout(len)).then(star(op.truediv))
+mean([1, 2, 3]) # 2.0
+```
+
+### *split*
+
+If you need to apply first value of a pair to a first function and a second value of the pair to a second function, you can use *split*:
+
+```python
+g = Function() / (lambda x: x + 1) ^ (lambda x: x * 2)
+g((2, 3)) # (3, 6)
+
+teams = {"team a": ["member 1", "member 2"], "team b": ["member 3"]}
+f = Function() / str.capitalize ^ len
+for t in teams.items():
+ print(f(t))
+
+# ('Team a', 2)
+# ('Team b', 1)
+
+# alternatively
+
+g = Function().then(lambda x: x + 1).split(lambda x: x * 2)
+g((2, 3)) # (3, 6)
+
+f = Function().then(str.capitalize).split(len)
+for t in teams.items():
+ print(f(t))
+
+# ('Team a', 2)
+# ('Team b', 1)
+```
+
+### *call*
+
+Sometimes you want to call a function ``inline'' after several compositions. In this case, you might use:
+
+```python
+(Function() / (lambda x: x * 2) / f)(5) # 11
+```
+
+But it might be hard to read. Especially, when you mostly pass lambdas. A better way to call a function is by using:
+
+```python
+Function() / (lambda x: x * 2) / f | 5 # 11
+```
+
+### *star* (function)
+
+Suppose, that you defined a function with multiple arguments such as:
+
+```python
+def f(x, y):
+ return x + y * x
+```
+
+And you want to wrap that function using Function. In this case, you have to use *star*.
+
+```python
+Function() @ star(f) | (1, 2) # 3
+```
+
+*star* simply passes arguments to a function using Python *\** (star) operator.
+
+### *unstar* (function)
+
+*unstar* is the opposite function to *star*:
+
+```python
+names = unstar(", ".join)("Haskell Curry", "John Smith", "George Sand")
+print(names) # Haskell Curry, John Smith, George Sand
+```
+
+### *foreach*
+
+You can also call a function for each value in some *Iterable* in the following way:
+
+```python
+ys = Function() / (lambda x: x * 2) / (lambda x: x + 1) * range(5)
+
+for y in ys:
+ print(y)
+
+# 1
+# 3
+# 5
+# 7
+# 9
+#
+```
+
+## *Pipeline*
+
+In order to create a *Pipeline*, you simply pass any *Iterable*:
+
+```python
+xs = Pipeline(range(5))
+
+# notation abuse, do not use that:
+
+xs = Function() / Pipeline | range(5)
+```
+
+You can also create an empty *Pipeline*:
+
+```python
+xs = Pipeline()
+```
+
+Under the hood *Pipeline* is simply uses *iter* on a passed *Iterable*. It means, that if you will pass an *Iterable*, that could be exhausted, you iterate over *Pipeline* only once.
+
+```python
+xs = Pipeline(range(2))
+
+for x in xs:
+ print(x)
+
+# 1
+# 2
+#
+
+# perfectly fine, because range(x) returns a special object
+for x in xs:
+ print(x)
+
+# 1
+# 2
+#
+
+xs = Pipeline(x for x in range(2))
+
+for x in xs:
+ print(x)
+
+# 1
+# 2
+#
+
+# xs already exhausted
+for x in xs:
+ print(x)
+```
+
+### *map*
+
+*map* allows you to call a *Callable*, which takes a single value and returns a single value, on each value of the *Pipeline*.
+
+```python
+ys = Pipeline(range(3)) / (lambda x: x + 1) / (lambda x: (x, x + 1)) / star(lambda x, y: x + y * x)
+
+for y in ys:
+ print(y)
+
+# 3
+# 8
+# 15
+#
+
+# alternatively
+
+ys = Pipeline(range(3)).map(lambda x: x + 1).map(lambda x: (x, x + 1)).map(star(lambda x, y: x + y * x))
+```
+
+### *filter*
+
+*filter* allows you to filter *Pipeline* values.
+
+```python
+ys = Pipeline(range(3)) % (lambda x: x > 1)
+
+for y in ys:
+ print(y)
+
+# 2
+#
+
+# alternatively
+
+ys = Pipeline(range(3)).filter(lambda x: x > 1)
+```
+
+### *flatmap*
+
+*flatmap* allows you to call a *Callable*, which takes a single value and returns an *Iterable*, on each value of the *Pipeline* and flatten results into single *Pipeline*.
+
+```python
+ys = Pipeline(range(2)) / (lambda x: x + 1) * (lambda x: (x, x + 1))
+
+for y in ys:
+ print(y)
+
+# 1
+# 2
+# 2
+# 3
+#
+
+# alternatively
+
+ys = Pipeline(range(2)).map(lambda x: x + 1).flatmap(lambda x: (x, x + 1))
+```
+
+### *apply*
+
+*apply* allows you to call a *Callable*, which takes an *Iterable* and returns an *Iterable*, on whole *Pipeline*.
+
+```python
+ys = Pipeline(range(2)) / (lambda x: x + 1) // tuple # internally Pipeline now has a tuple
+
+for y in ys:
+ print(y)
+
+# 1
+# 2
+#
+
+# now multiple itertations is possible
+for y in ys:
+ print(y)
+
+# 1
+# 2
+#
+
+# alternatively
+
+ys = Pipeline(range(2)).map(lambda x: x + 1).apply(tuple)
+```
+
+### *collect*
+
+*collect* allows you to call a *Callable*, which takes an *Iterable* and returns any single value, on whole *Pipeline*. The difference between *apply* and *collect* is that *collect* returns the result of a function instead of wrapping it with *Pipeline*.
+
+```python
+ys = Pipeline(range(2)) / (lambda x: x + 1) >> tuple
+print(ys)
+
+# (1, 2)
+#
+
+# alternatively
+
+ys = Pipeline(range(2)).map(lambda x: x + 1).collect(tuple)
+```
+
+You can also combine *collect* with any function which takes an *Iterator*:
+
+```python
+ys = Pipeline(range(2)) / (lambda x: x + 1) >> next_
+print(ys) # (1,)
+
+ys = Pipeline(range(2)) % (lambda x: x == 5) >> next_
+print(ys) # ()
+
+ys = Pipeline(range(5)) >> shift(islice, 2)
+
+for y in ys:
+ print(y)
+
+# 0
+# 1
+
+# alternatively you can use apply
+
+ys = Pipeline(range(5)) // shift(islice, 2) | print
+
+# 0
+# 1
+```
+
+### *foreach*
+
+*foreach* allows you to call a *Callable*, which takes a single value, on each value of the *Pipeline*.
+
+```python
+Pipeline(range(2)) / (lambda x: x + 1) | print
+
+# 1
+# 2
+#
+
+# alternatively
+
+Pipeline(range(2)).map(lambda x: x + 1).foreach(print)
+```
+
+## *groupon*
+
+*groupon* is a function which takes a function *Callable[[X], Y]*, and returns some function which takes *Iterable[X]* and returns *Iterable[X]* grouped on *Callable[[X], Y]* function. The *groupon* function uses Python *groupby* function under the hood. *groupon* adds a grouping key using passed *Callable[[X], Y]* function and sorts values by that key before applying *groupby*.
+
+```python
+xs = -3, 1, 0, -1, 5
+
+(
+ Pipeline(xs)
+ // groupon(lambda x: x > 0)
+ / value(tuple)
+ | print
+)
+
+# (False, (-3, 0, -1))
+# (True, (1, 5))
+```
+
+## *nonion.tools*
+
+### *Either*
+
+*Either* is a type alias. *Either* is defined as follows:
+
+```python
+Either = Tuple[Maybe[X], Maybe[Y]]
+```
+
+*Either* can be used when you need to return either left (bad) value or a right (good) value:
+
+```python
+def readline(path: str) -> Either[str, str]:
+ h: Maybe[IOBase] = try_(open)(path)
+
+ if not h:
+ return (("error occurred during open",), ())
+
+ h, *_ = h
+ line = h.readline()
+ h.close()
+
+ return ((), line)
+
+error, line = readline("requirements.txt")
+
+if line:
+ print(*line)
+else:
+ print(*error)
+```
+
+Because *Either* is simply a type alias, it does not checks whether only left or only right value is passed.
+
+### *Maybe*
+
+*Maybe* is a type alias. *Maybe* resembles Haskell's *Maybe* in Python. *Maybe* is defined as follows:
+
+```python
+Maybe = Union[Tuple[X], Tuple[()]]
+```
+
+As we can see *Maybe* is simply some *tuple* that might contain a single value or be an empty *tuple*.
+It means that in order to initialize an *Maybe* you can simply write:
+
+```python
+x = () # empty Maybe
+y = (3,) # Maybe with value 3
+```
+
+You can easily check whether an *Maybe* is empty:
+
+```python
+def f(x: int) -> Maybe[int]:
+ return (x,) if x < 3 else ()
+
+x: Maybe[int] = f(5)
+
+if not x:
+ print("Maybe is empty") # Maybe is empty
+```
+
+You can also provide an alternative value if *Maybe* is empty and immediately try to unwrap the *Maybe*:
+
+```python
+x: Maybe[int] = f(5)
+y, *_ = x or (42,)
+
+print(y) # 42
+```
+
+```python
+# alternatively
+
+x: Maybe[int] = f(1)
+z = x or (42,)
+
+# notice: if you pass an empty *z to a single argument function, you will get an error
+print(*z) # 1
+```
+
+Because *Maybe* is simply a *tuple* under the hood, you can apply any Python function (that operates on *tuple*) to an instance of an *Maybe*.
+
+### *as_catch*
+
+*as_catch* is simply:
+
+```python
+@curry
+def as_catch(default: Callable[[X], Y], xys: Iterable[Tuple[X, Y]]) -> Callable[[X], Y]:
+ return catch(as_match(xys), default=default)
+```
+
+Example of *as_catch* usage:
+
+```python
+successor: Callable[[int], int] = Pipeline(range(10)) // zipmapr(lambda x: x + 1) >> as_catch(lambda _: -1)
+print(successor(1)) # 2
+print(successor(100)) # -1
+```
+
+### *as_match*
+
+*as_match* is simply:
+
+```python
+def as_match(xys: Iterable[Tuple[X, Y]]) -> Callable[[X], Maybe[Y]]:
+ x_to_y = dict(xys)
+
+ def lookup(x: X) -> Maybe[Y]:
+ return (x_to_y[x],) if x in x_to_y else ()
+
+ return lookup
+```
+
+Example of *as_match* usage:
+
+```python
+successor: Callable[[int], Maybe[int]] = Pipeline(range(10)) // zipmapr(lambda x: x + 1) >> as_match
+print(successor(1)) # (2,)
+print(successor(100)) # ()
+```
+
+### *between*
+
+*between* is simply:
+
+```python
+def between(low: float, high: float) -> Callable[[float], bool]:
+ return lambda x: low <= x and x <= high
+```
+
+Example of *between* usage:
+
+```python
+ys = filter(between(3, 5), range(10))
+print(tuple(ys)) # (3, 4, 5)
+```
+
+### *both*
+
+*both* is a function that takes a function *Callable[[X], Y]* and returns a function *Callable[[Tuple[X, X]], Tuple[Y, Y]]*. The returned function takes a pair and applies *Callable[[X], Y]* to both values.
+
+```python
+both(lambda x: x + 1)((1, 2)) # (2, 3)
+```
+
+### *cache*
+
+*cache* is a decorator which returns a function that always returns a value that was returned in the first call.
+
+```python
+def f(x: int) -> int:
+ return x + 5
+
+g = cache(f)
+print(g(5)) # 10
+print(g()) # 10
+print(g("abc", 1, {})) # 10
+
+h = cache(f)
+print(h(7)) # 12
+```
+
+### *catch*
+
+*catch* is a function that resembles pattern-matching in Python. It takes some functions `*fs: Callable[..., Maybe[Y]]` with some catch-all function `default: Callable[..., Y]` and returns a function `Callable[..., Y]` which executes `fs` functions one by one until some function will return non-empty `Maybe[Y]`. If none of those functions will return a non-empty `Maybe[Y]`, the result of `default` function is returned.
+
+```python
+# let's say that we want to parse age ranges that we have in our data:
+age_ranges = (
+ "10-20",
+ "20-30",
+ "30+",
+ "60+",
+ "invalid input"
+)
+
+# we consider 30+ to be a valid range <30, 100)
+
+def parse_range(x: str) -> Tuple[int, int]:
+ raw = x.split("-")
+ low, high, *_ = map(int, raw)
+
+ return low, high
+
+def parse_unbounded_range(x: str) -> Tuple[int, int]:
+ raw, *_ = x.split("+")
+ return int(raw), 100
+
+# we will use <18, 100) as our default range
+parse = catch(
+ try_(parse_range),
+ try_(parse_unbounded_range),
+ default=lambda _: (18, 100)
+)
+
+for x in age_ranges:
+ print(parse(x))
+
+# (10, 20)
+# (20, 30)
+# (30, 100)
+# (60, 100)
+# (18, 100)
+```
+
+### *compose*
+
+*compose* is an implementation of a ``Function composition" defined as $( f \circ g )(x) = f(g(x))$.
+
+```python
+xs = "a", "ab", "c"
+yxs = enumerate(xs)
+
+p: Callable[[Tuple[int, str]], bool] = compose(lambda x: x.startswith("a"), snd)
+filtered: Iterable[Tuple[int, str]] = filter(p, yxs)
+
+ys = map(fst, filtered)
+print(tuple(ys)) # (0, 1)
+```
+
+### *cons*
+
+*cons* allows you to prepend value of type *X* to an *Iterable[X]*.
+
+```python
+print(tuple(cons(1)((2, 3, 4, 5)))) # (1, 2, 3, 4, 5)
+```
+
+### *const*
+
+*const* is a function that takes a value of type *X* and returns a function of type *Callable[[Y], X]*. The returned function will ignore its argument and will return the value that was passed to *const*.
+
+```python
+print(const(1)("abc")) # 1
+```
+
+### *curry*
+
+*curry* is simply:
+
+```python
+def curry(f: Callable[..., Y]) -> Callable[..., Y]:
+ return lambda *args, **kwargs: partial(f, *args, **kwargs)
+```
+
+### *cycle*
+
+*cycle* is a function which takes *Tuple[X, ...]* and returns *Iterable[X]*. *Iterable[X]* is created by repeatedly yielding elements from passed *Tuple[X, ...]*.
+
+```python
+xs = take(10)(cycle([1, 2, 3]))
+print(tuple(xs)) # (1, 2, 3, 1, 2, 3, 1, 2, 3, 1)
+```
+
+### *drop*
+
+*drop* is simply:
+
+```python
+def drop(n: int) -> Callable[[Iterable[X]], Iterable[X]]:
+ return (lambda xs: islice(xs, n, None)) if n > 0 else (lambda _: ())
+```
+
+Example of *drop* usage:
+
+```python
+xs = drop(1)(range(3))
+print(tuple(xs)) # (1, 2)
+
+xs = islice(range(3), 1, None)
+print(tuple(xs)) # (1, 2)
+```
+
+### *either*
+
+*either* is a function that takes a function of type *Callable[[X], Z]* and a function of type *Callable[[Y], Z]* and returns a function of type *Callable[[Either[X, Y]], Z]*. The returned function takes *Either[X, Y]*. If the *Either* contains left value, the value will be applied to *Callable[[X], Z]*. If the *Either* contains right value, the value will be applied to *Callable[[Y], Z]*. The result of application is returned.
+
+```python
+f = either(lambda x: f"Error: {x}", lambda y: f"OK: {y}")
+print(f((("unable to parse",), ()))) # Error: unable to parse
+print(f(((), (1,)))) # OK: 1
+```
+
+### *either_to_maybe*
+
+*either_to_maybe* is an alias for *snd*.
+
+### *except_*
+
+*except_* is a decorator which returns a function that returns *Either* with some value or an *Exception* that was raised.
+
+```python
+f = except_(next)
+xs = iter(range(2))
+
+print(f(xs)) # ((), (0,))
+print(f(xs)) # ((), (1,))
+print(f(xs)) # ((StopIteration(),), ())
+```
+
+### *fail*
+
+*fail* is a function which takes a function *Callable[[Exception], Y]* and returns a decorator which takes a function *Callable[..., Y]* and returns *Callable[..., Y]*. The function returned by the decorator uses passed *Callable[[Exception], Y]* to handle possible errors produced by a decorated function. If no errors produced, *Callable[[Exception], Y]* will not be executed and the result of the decorated function will be returned.
+
+```python
+# Let's say that you want to write is_repeated function
+# which tells you whether you have a collection consisting
+# only from the single value.
+
+# The simplest function you could think of might look like this:
+
+def is_repeated(xs: Iterable[X]) -> bool:
+ x, *rest = xs
+ return all(x == y for y in rest)
+
+# It works on collections that have at least one value:
+
+print(is_repeated((1, 1))) # True
+print(is_repeated((1, 2, 3))) # False
+
+# but when you have an empty collection, this function will result
+# in an error:
+
+print(is_repeated(()))
+# ValueError: not enough values to unpack (expected at least 1, got 0)
+
+# In order to handle this case, you can rewrite this function in a
+# following manner:
+
+def is_repeated(xs: Iterable[X]) -> bool:
+ xs = iter(xs)
+ wrapped_x = next_(xs)
+
+ if wrapped_x:
+ x, *_ = wrapped_x
+ return all(x == y for y in xs)
+ else: return True
+
+# And it would work:
+
+print(is_repeated((1, 1))) # True
+print(is_repeated((1, 2, 3))) # False
+print(is_repeated(())) # True
+
+# You might also use a *fail* function which will surround your
+# function with try-except clause, to deal with empty collection.
+
+@fail(lambda _: True)
+def is_repeated(xs: Iterable[X]) -> bool:
+ x, *rest = xs
+ return all(x == y for y in rest)
+
+# In case when error is raised by is_repeated, the
+# lambda _: True
+# function will be executed. The raised error will be passed to
+# that function.
+
+def g(e: Exception) -> bool:
+ print(e)
+ return True
+
+@fail(g)
+def is_repeated(xs: Iterable[X]) -> bool:
+ x, *rest = xs
+ return all(x == y for y in rest)
+
+print(is_repeated((1,))) # True
+print(is_repeated(()))
+# not enough values to unpack (expected at least 1, got 0)
+# True
+```
+
+### *find*
+
+*find* is a function which takes a predicate and returns a function which takes some *Iterable* and returns an *Maybe* with value that matches the predicate if such value exists:
+
+```python
+x: Maybe[int] = find(lambda x: x == 3)(range(5))
+print(x) # (3,)
+
+x: Maybe[int] = find(lambda x: x == -1)(range(5))
+print(x) # ()
+```
+
+### *findindex*
+
+*findindex* is a function that works like *find*, but instead of returning a function which returns a value in *Iterable* that matches some predicate, it returns a function which returns an index of that value in *Iterable*.
+
+```python
+x: Maybe[int] = findindex(lambda x: x == 8)(range(5, 10))
+print(x) # (3,)
+
+x: Maybe[int] = findindex(lambda x: x == -1)(range(5, 10))
+print(x) # ()
+```
+
+### *finds*
+
+*finds* is a function which takes an *Iterable[Callable[[X], bool]]* of predicates and returns a function which takes some *Iterable[X]* and returns an *Iterable[Maybe[X]]*. *finds* iterates over each predicate and searches for a matching value for that predicate in the passed *Iterable[X]*. *finds* will store checked *Iterable[X]* values in a buffer, so that the buffer will be checked at first and (if needed) the remaining *Iterable[X]* will be checked at last.
+
+```python
+fs = (lambda x: x == 2), (lambda x: x == 4), (lambda x: x == 1), (lambda x: x == -1)
+ys: Iterable[Maybe[int]] = finds(fs)(range(5))
+
+for y in ys:
+ print(y)
+
+# (2,)
+# (4,)
+# (1,)
+# ()
+```
+
+### *flattenl*
+
+*flattenl* is a function which takes a *Tuple* which contains another *Tuple* on the beginning and flattens that inner *Tuple* inside of outer *Tuple*.
+
+```python
+xys = {"A": 2.5, "B": 3.14}
+Pipeline(xys.items()) // zipr(count(1)) / flattenl | print
+
+# ('A', 2.5, 1)
+# ('B', 3.14, 2)
+```
+
+### *flattenr*
+
+*flattenr* is a function which takes a *Tuple* which contains another *Tuple* on the end and flattens that inner *Tuple* inside of outer *Tuple*.
+
+```python
+xys = {"A": 2.5, "B": 3.14}
+Pipeline(xys.items()) // zipl(count(1)) / flattenr | print
+
+# (1, 'A', 2.5)
+# (2, 'B', 3.14)
+```
+
+### *flip*
+
+*flip* is simply:
+
+```python
+def flip(f: Callable[[Y, X], Z]) -> Callable[[X, Y], Z]:
+ return lambda x, y: f(y, x)
+```
+
+Example of *flip* usage:
+
+```python
+xs = "A", "B", "C"
+Pipeline(enumerate(xs)) / key(lambda x: x + 1) * star(flip(repeat)) | print
+
+# A
+# B
+# B
+# C
+# C
+# C
+```
+
+### *foldl*
+
+*foldl* is a function which takes a binary function *Callable[[Y, X], Y]* and some accumulator *Y* and returns a function which takes *Iterable[X]* and returns *Y*. This function allows you to fold *Iterable[X]* from __left__ using passed binary function. The accumulator is being passed as the __first__ argument of the binary function.
+
+Example of *foldl* usage:
+
+```python
+xs = range(ord("A"), ord("Z") + 1)
+alphabet = Pipeline(xs) / chr >> foldl(operator.add, "")
+
+print(alphabet)
+
+# ABCDEFGHIJKLMNOPQRSTUVWXYZ
+```
+
+### *foldl1*
+
+*foldl1* is a similar function to *foldl*. The difference between *foldl1* and *foldl* is that *foldl1* takes *Callable[[X, X], X]*, uses *Iterable[X]* __first__ element as the accumulator and returns *X*. *foldl1* will raise an error if the supplied *Iterable[X]* is empty.
+
+### *foldr*
+
+*foldr* is a function which takes a binary function *Callable[[X, Y], Y]* and some accumulator *Y* and returns a function which takes *Iterable[X]* and returns *Y*. This function allows you to fold *Iterable[X]* from __right__ using passed binary function. The accumulator is being passed as the __last__ argument of the binary function.
+
+Example of *foldr* usage:
+
+```python
+xs = range(ord("A"), ord("Z") + 1)
+reversed_alphabet = (
+ Pipeline(xs)
+ / chr
+ // foldr(lambda x, acc: acc + [x], [])
+ >> foldl(operator.add, "")
+)
+
+print(reversed_alphabet)
+
+# ZYXWVUTSRQPONMLKJIHGFEDCBA
+```
+
+### *foldr1*
+
+*foldr1* is a similar function to *foldr*. The difference between *foldr1* and *foldr* is that *foldr1* takes *Callable[[X, X], X]*, uses *Iterable[X]* __last__ element as the accumulator and returns *X*. *foldr1* will raise an error if the supplied *Iterable[X]* is empty. Under the hood *foldr1* will use *tuple* on passed *Iterable[X]* in order to extract the accumulator.
+
+### *fst*
+
+*fst* is simply:
+
+```python
+def fst(xy: Tuple[X, Y]) -> X:
+ return xy[0]
+```
+
+### *group*
+
+*group* is a function which takes *Iterable[X]* and returns *Iterable[Tuple[X, ...]]*. This function groups passed elements by equality comparison `==`.
+
+```python
+xs = 1, 1, 2, 2, 2, 3, 1, 1, 1
+print(tuple(group(xs))) # ((1, 1), (2, 2, 2), (3,), (1, 1, 1))
+```
+
+### *groupby*
+
+*groupby* is a function which takes an equality comparison function *Callable[[X, X], bool]* and returns a function *Callable[[Iterable[X]], Iterable[Tuple[X, ...]]]* which groups passed elements by the equality comparison function.
+
+```python
+people = (
+ ("Alex", 23),
+ ("John", 23),
+ ("Sam", 27),
+ ("Kate", 27),
+ ("Fred", 23),
+)
+
+grouped = groupby(lambda x, y: snd(x) == snd(y))(people)
+print(tuple(grouped))
+# ((('Alex', 23), ('John', 23)), (('Sam', 27), ('Kate', 27)), (('Fred', 23),))
+
+# or you can use *on* function:
+
+grouped = groupby(on(operator.eq, snd))(people)
+print(tuple(grouped))
+# ((('Alex', 23), ('John', 23)), (('Sam', 27), ('Kate', 27)), (('Fred', 23),))
+```
+
+### *in_*
+
+*in_* is simply:
+
+```python
+def in_(xs: Tuple[X, ...]) -> Callable[[X], bool]:
+ return lambda x: x in xs
+```
+
+### *key*
+
+*key* is simply:
+
+```python
+def key(f: Callable[[X], Z]) -> Callable[[Tuple[X, Y]], Tuple[Z, Y]]:
+ g: Callable[[Tuple[X, Y]], Z] = compose(f, fst)
+ return lambda xy: (g(xy), snd(xy))
+```
+
+Example of *key* usage:
+
+```python
+xys = {"A": [1, 2, 3], "B": [3, 4]}
+zys = map(key(str.casefold), xys.items())
+
+for zy in zys:
+ print(zy)
+
+# ('a', [1, 2, 3])
+# ('b', [3, 4])
+```
+
+### *length*
+
+*length* is a function which takes an *Iterable* and returns number of elements in that *Iterable*. *length* exhausts the *Iterable*.
+
+```python
+xs = 1, 2, 3
+print(len(xs)) # 3
+
+# len(iter(xs)) will raise an error
+print(length(iter(xs))) # 3
+```
+
+### *lift*
+
+*lift* is simply:
+
+```python
+lift = curry(map)
+```
+
+### *match_*
+
+*match_* is a function that resembles pattern-matching in Python. It takes some functions `*fs: Callable[..., Maybe[Y]]` and returns a function `Callable[..., Maybe[Y]]` which executes `fs` functions one by one until some function will return non-empty `Maybe[Y]`. If none of those functions will return a non-empty `Maybe[Y]`, an empty `Maybe[Y]` (i.e. `()`) is returned.
+
+```python
+# let's say that we want to parse age ranges that we have in our data:
+age_ranges = (
+ "10-20",
+ "20-30",
+ "30+",
+ "60+",
+ "invalid input"
+)
+
+# we consider 30+ to be a valid range <30, 100)
+
+def parse_range(x: str) -> Tuple[int, int]:
+ raw = x.split("-")
+ low, high, *_ = map(int, raw)
+
+ return low, high
+
+def parse_unbounded_range(x: str) -> Tuple[int, int]:
+ raw, *_ = x.split("+")
+ return int(raw), 100
+
+parse = match_(
+ try_(parse_range),
+ try_(parse_unbounded_range)
+)
+
+for x in age_ranges:
+ print(parse(x))
+
+# ((10, 20),)
+# ((20, 30),)
+# ((30, 100),)
+# ((60, 100),)
+# ()
+```
+
+### *maybe*
+
+*maybe* is a function that takes a function of type *Callable[[], Y]* and a function of type *Callable[[X], Y]* and returns a function of type *Callable[[Maybe[X]], Y]*. The returned function takes *Maybe[X]*. If the *Maybe* contains value, the value will be applied to *Callable[[X], Y]* and a result of application is returned. If the *Maybe* contains no value, the *Callable[[], Y]* is called and a result is returned.
+
+```python
+f = maybe(lambda: "Error", lambda x: f"OK: {x}")
+print(f(())) # Error
+print(f((1,))) # OK: 1
+```
+
+### *maybe_to_either*
+
+*maybe_to_either* is a function which allows you to create an *Either[Y, X]* from an *Maybe[X]*. *maybe_to_either* takes a function *Callable[[], Y]* and returns a function *Callable[[Maybe[X]], Either[Y, X]]*.
+
+```python
+raw_numbers = "1\n22\nten\n333".splitlines()
+
+xs = (
+ Pipeline(raw_numbers)
+ / try_(int)
+ / maybe_to_either(lambda: f"Failed to parse.")
+ | print
+)
+
+# ((), (1,))
+# ((), (22,))
+# (('Failed to parse.',), ())
+# ((), (333,))
+```
+
+The difference between using *maybe_to_either* and explicitly creating *Either* using tuples is that *maybe_to_either* will not evaluate the left part if the right part is present. That is why *Callable[[], Y]* is being passed to *maybe_to_either* instead of *Y*.
+
+### *merge*
+
+*merge* is a function which takes two sorted *Iterable[X]* and merges them into single sorted *Iterable[X]*. It uses *lambda x, y: x <= y* comparison function. Use *key* parameter to specify a function *Callable[[X], Y]* to be called on each element prior to making comparisons.
+
+```python
+xs = merge((1, 3, 5), (1, 2, 4))
+print(tuple(xs)) # (1, 1, 2, 3, 4, 5)
+
+xs, ys = [(1, "a"), (3, "d"), (5, "f")], [(1, "b"), (2, "c"), (4, "e")]
+print(tuple((merge(xs, ys, key=fst))))
+# ((1, 'a'), (1, 'b'), (2, 'c'), (3, 'd'), (4, 'e'), (5, 'f'))
+```
+
+### *next_*
+
+*next_* is simply:
+
+```python
+next_: Callable[[Iterator[X]], Maybe[X]] = try_(next)
+```
+
+Example of *next_* usage:
+
+```python
+xs = iter(range(2))
+
+print(next_(xs)) # (0,)
+print(next_(xs)) # (1,)
+print(next_(xs)) # ()
+```
+
+### *not_*
+
+*not_* is a function which takes a predicate and returns negation of that predicate.
+
+```python
+print(not_(lambda x, y: x == y)(1, 5)) # True
+```
+
+### *on*
+
+*on* is simply:
+
+```python
+def on(f: Callable[[Y, Y], Z], g: Callable[[X], Y]) -> Callable[[X, X], Z]:
+ return lambda p, n: f(g(p), g(n))
+```
+
+Example of *on* usage could be found in *groupby* section.
+
+### *padl*
+
+*padl* is a function which allows you to pad some *Iterable* from the __left__ using a filler. This function takes a number *n* and a filler *x*. In case when **exact=False** option is passed, it returns a function which prepends *n - k* fillers to the passed *Iterable*, where *k* is a length of the passed *Iterable*. In case when **exact=True** option is passed, it returns a function which prepends __exactly__ *n* fillers to the passed *Iterable*.
+
+```python
+xs = "".join(padl(5, "x")("abc"))
+print(xs) # xxabc
+
+xs = "".join(padl(5, "x", exact=True)("abc"))
+print(xs) # xxxxxabc
+```
+
+### *padr*
+
+*padr* is a function which allows you to pad some *Iterable* from the __right__ using a filler. This function takes a number *n* and a filler *x*. In case when **exact=False** option is passed, it returns a function which appends *n - k* fillers to the passed *Iterable*, where *k* is a length of the passed *Iterable*. In case when **exact=True** option is passed, it returns a function which appends __exactly__ *n* fillers to the passed *Iterable*.
+
+```python
+xs = "".join(padr(5, "x")("abc"))
+print(xs) # abcxx
+
+xs = "".join(padr(5, "x", exact=True)("abc"))
+print(xs) # abcxxxxx
+```
+
+### *partition*
+
+*partition* is a function which takes a predicate and returns a function *Callable[[Iterable[X]], Tuple[Tuple[X, ...], Tuple[X, ...]]]*. This returned function splits passed elements into those that do match the predicate and the rest. The difference between *span* and *partition* is that *span* stops when it finds the first element that does not match the predicate and *partition* goes until the end.
+
+```python
+xs = 1, 1, 2, 2, 2, 3, 1, 1, 1
+matched, rest = partition(lambda x: x == 1)(xs)
+
+print(matched) # (1, 1, 1, 1, 1)
+print(rest) # (2, 2, 2, 3)
+```
+
+### *peek*
+
+*peek* is a decorator which allows you to apply some function to a passed argument and return back the passed argument instead of a function's result.
+
+```python
+x = peek(print)(5) # 5
+print(x) # 5
+
+xs = (
+ Pipeline(range(3, 0, -1))
+ / peek(print, "Countdown:", file=sys.stderr)
+ >> tuple
+)
+# Countdown: 3
+# Countdown: 2
+# Countdown: 1
+
+print(xs) # (3, 2, 1)
+```
+
+### *pick*
+
+*pick* is a function which takes some aggregate function *Callable[[Tuple[X, ...]], X]* and returns a function *Callable[[Iterable[X]], Iterable[X]]*. This returned function __picks__ all elements from the passed collection *Iterable[X]* which are equal to the value returned by the aggregate function. The equal function might be substituted with any other function of type signature *Callable[[X, X], bool]* by passing a *compare* parameter.
+
+```python
+print(min([1, 2, 1, 1, 3, 1])) # 1
+
+ys = tuple(pick(min)([1, 2, 1, 1, 3, 1]))
+print(ys) # (1, 1, 1, 1)
+
+print(min([]))
+# ValueError: min() arg is an empty sequence
+
+ys = tuple(pick(min)([]))
+print(ys) # ()
+```
+
+### *pickby*
+
+*pickby* is a function which takes a function *Callable[[X], Y]*, an aggregate function *Callable[[Tuple[Y, ...]], Y]* and returns a function *Callable[[Iterable[X]], Iterable[X]]*. This returned function __picks__ all elements from the passed collection *Iterable[X]* which corresponding *Y* values, created by the *Callable[[X], Y]* function, are equal to the value returned by the aggregate function. The equal function might be substituted with any other function of type signature *Callable[[Y, Y], bool]* by passing a *compare* parameter. The function *Callable[[X], Y]* will be used exactly once on the whole collection.
+
+```python
+cars_and_prices = (
+ ("Audi", 25000),
+ ("BMW", 70000),
+ ("Mercedes", 25000),
+)
+
+cheapest_car = min(cars_and_prices, key=snd)
+print(cheapest_car) # ('Audi', 25000)
+
+cheapest_cars = pickby(snd, min)(cars_and_prices)
+print(tuple(cheapest_cars)) # (('Audi', 25000), ('Mercedes', 25000))
+```
+
+### *powerset*
+
+*powerset* is a function which takes a *Tuple[X, ...]* and produces power set of those elements in form of *Iterable[Iterable[X]]*.
+
+```python
+xs = tuple(range(3))
+ps = tuple(map(tuple, powerset(xs)))
+
+print(ps) # ((), (2,), (1,), (1, 2), (0,), (0, 2), (0, 1), (0, 1, 2))
+```
+
+### *replicate*
+
+*replicate* is a function which takes a number *n* and returns a function, which takes some value *x* and repeats *n* times value *x*.
+
+```python
+xs = tuple(replicate(5)("hello"))
+print(xs)
+# ('hello', 'hello', 'hello', 'hello', 'hello')
+```
+
+### *reverse*
+
+*reverse* is a function which takes an *Iterable[X]* and returns *Deque[X]* which contains elements from *Iterable[X]* in reversed order.
+
+```python
+xs = range(ord("A"), ord("Z") + 1)
+
+reversed_alphabet = (
+ Pipeline(xs)
+ / chr
+ // reverse # Python reversed would not work on Iterable
+ >> foldl(operator.add, "")
+)
+
+print(reversed_alphabet)
+
+# ZYXWVUTSRQPONMLKJIHGFEDCBA
+```
+
+### *scanl*
+
+*scanl* is a similar function to *foldl*. The difference between *scanl* and *foldl* is that *scanl* instead of returning a function which takes *Iterable[X]* and returns *Y*, returns a function which takes *Iterable[X]* and returns *Iterable[Y]*. The resulting *Iterable[Y]* contains all accumulators used in *foldl*.
+
+```python
+xs = scanl(operator.mul, 1)((1, 2, 3, 4, 5))
+print(tuple(xs))
+# (1, 1, 2, 6, 24, 120)
+```
+
+### *scanl1*
+
+*scanl1* is a similar function to *foldl1*. The difference between *scanl1* and *foldl1* is that *scanl1* instead of returning a function which takes *Iterable[X]* and returns *X*, returns a function which takes *Iterable[X]* and returns *Iterable[X]*. The resulting *Iterable[X]* contains all accumulators used in *foldl1*.
+
+```python
+xs = scanl1(operator.mul)((1, 2, 3, 4, 5))
+print(tuple(xs))
+# (1, 2, 6, 24, 120)
+```
+
+### *scanr*
+
+*scanr* is a similar function to *foldr*. The difference between *scanr* and *foldr* is that *scanr* instead of returning a function which takes *Iterable[X]* and returns *Y*, returns a function which takes *Iterable[X]* and returns *Deque[Y]*. The resulting *Deque[Y]* contains all accumulators used in *foldr*.
+
+```python
+xs = scanr(operator.mul, 1)((1, 2, 3, 4, 5))
+print(xs)
+# deque([120, 120, 60, 20, 5, 1])
+```
+
+### *scanr1*
+
+*scanr1* is a similar function to *foldr1*. The difference between *scanr1* and *foldr1* is that *scanr1* instead of returning a function which takes *Iterable[X]* and returns *X*, returns a function which takes *Iterable[X]* and returns *Deque[X]*. The resulting *Deque[X]* contains all accumulators used in *foldr1*.
+
+```python
+xs = scanr1(operator.mul)((1, 2, 3, 4, 5))
+print(xs)
+# deque([120, 120, 60, 20, 5])
+```
+
+### *search*
+
+*search* is a function which takes a predicate *Callable[[X], bool]* along with *Iterable[Y]* and returns a function which takes *Iterable[X]* and returns *Iterable[Y]*. This function zips *Iterable[Y]* with *Iterable[X]* and returns those *Y*s for which corresponding *X*s match the predicate.
+
+```python
+xs = search(lambda x: x > 3, count())(range(1, 6))
+print(tuple(xs))
+# (3, 4)
+```
+
+### *shift*
+
+*shift* is a decorator which returns a partially applied function. The difference between Python's *functools.partial* and *shift* is that *shift* will return a function which prepends *\*args* and *\*\*kwargs*:
+
+```python
+def dummy(*args: object, **kwargs: object):
+ print(args)
+ print(kwargs)
+
+partial(dummy, 1, 2, a=1, b="b")(3, 4, c="c")
+print("-" * 10)
+shift(dummy, 1, 2, a=1, b="b")(3, 4, c="c")
+
+# (1, 2, 3, 4)
+# {'a': 1, 'b': 'b', 'c': 'c'}
+# ----------
+# (3, 4, 1, 2)
+# {'c': 'c', 'a': 1, 'b': 'b'}
+```
+
+Example of *shift* usage:
+
+```python
+take_3 = shift(islice, 3)
+xs: Iterable[int] = take_3(range(5))
+
+for x in xs:
+ print(x)
+
+# 0
+# 1
+# 2
+```
+
+### *slide*
+
+*slide* is a function which takes a sliding window length **n** and a **step**, and returns a function which takes an *Iterable* and applies sliding window over it resulting in an *Iterable* of *tuple*s. Each *tuple* has at most length equal to **n**. In case when **exact=True** option is passed, each *tuple* has length equal to **n**. **step** is simply a shift of a sliding window.
+
+```python
+xs: Iterable[Tuple[int, ...]] = slide()(range(10))
+print(tuple(xs))
+# ((0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 7), (7, 8), (8, 9), (9,))
+
+xs: Iterable[Tuple[int, ...]] = slide(n=3, step=2)(range(10))
+print(tuple(xs))
+# ((0, 1, 2), (2, 3, 4), (4, 5, 6), (6, 7, 8), (8, 9))
+
+xs: Iterable[Tuple[int, ...]] = slide(n=3, step=2, exact=True)(range(10))
+print(tuple(xs))
+# ((0, 1, 2), (2, 3, 4), (4, 5, 6), (6, 7, 8))
+
+def is_sorted(xs: Iterable[X], compare: Callable[[X, X], bool] = operator.le) -> bool:
+ return (
+ Pipeline(slide(exact=True)(xs))
+ / star(compare)
+ >> all
+ )
+
+print(is_sorted((1, 2, 5))) # True
+print(is_sorted((1, 2, -5))) # False
+```
+
+### *snd*
+
+*snd* is simply:
+
+```python
+def snd(xy: Tuple[X, Y]) -> Y:
+ return xy[1]
+```
+
+### *span*
+
+*span* is a function which takes a predicate and returns a function *Callable[[Iterable[X]], Tuple[Tuple[X, ...], Iterable[X]]]*. This returned function splits passed elements into those that do match the predicate on the beginning and the rest.
+
+```python
+xs = 1, 1, 2, 2, 2, 3, 1, 1, 1
+matched, rest = span(lambda x: x == 1)(xs)
+
+print(matched) # (1, 1)
+print(tuple(rest)) # (2, 2, 2, 3, 1, 1, 1)
+```
+
+### *splitat*
+
+*splitat* is a function which takes an index *i* and returns a function which splits an *Iterable[X]* into *Tuple[X, ...]* and *Iterable[X]*. The *Tuple[X, ...]* will contain first *i* elements and the *Iterable[X]* will contain the rest.
+
+```python
+xs, rest = splitat(1)(range(5))
+print(xs) # (0,)
+print(tuple(rest)) # (1, 2, 3, 4)
+```
+
+### *strip*
+
+*strip* is a function which takes an *Iterable[X]* and returns an *Iterable[X]* with removed consecutive duplicates. *strip* functions uses only equality comparison `==`.
+
+```python
+xs = 1, 1, 2, 2, 2, 3, 1, 1, 1
+print(tuple(strip(xs))) # (1, 2, 3, 1)
+```
+
+### *stripby*
+
+*stripby* is a function which takes an equality comparison function *Callable[[X, X], bool]* and returns a function *Callable[[Iterable[X]], Iterable[X]]* which removes consecutive duplicates in terms of the equality comparison function.
+
+```python
+people = (
+ ("Alex", 23),
+ ("John", 23),
+ ("Sam", 27),
+ ("Kate", 27),
+ ("Fred", 23),
+)
+
+stripped = stripby(lambda x, y: snd(x) == snd(y))(people)
+print(tuple(stripped))
+# (('Alex', 23), ('Sam', 27), ('Fred', 23))
+
+# or you can use *on* function:
+
+stripped = stripby(on(operator.eq, snd))(people)
+print(tuple(stripped))
+# (('Alex', 23), ('Sam', 27), ('Fred', 23))
+```
+
+### *take*
+
+*take* is simply:
+
+```python
+def take(n: int) -> Callable[[Iterable[X]], Iterable[X]]:
+ return (lambda xs: islice(xs, n)) if n > 0 else (lambda _: ())
+```
+
+Example of *take* usage:
+
+```python
+xs = take(1)(range(3))
+print(tuple(xs)) # (0,)
+
+xs = islice(range(3), 1)
+print(tuple(xs)) # (0,)
+```
+
+### *try_*
+
+*try_* is a decorator which returns a function that returns *Maybe* with some value or an empty *Maybe* if an *Exception* was raised.
+
+```python
+load_json = try_(json.loads)
+
+print(load_json("{}")) # ({},)
+print(load_json("[1, 2, 3]")) # ([1, 2, 3],)
+print(load_json("abc")) # ()
+```
+
+### *unfoldr*
+
+*unfoldr* is a function that takes a function of type *Callable[[X], Maybe[Tuple[Y, X]]]* and returns a function of type *Callable[[X], Iterable[Y]]*. The returned function will repeatedly apply *X* to the function passed to *unfoldr* until it returns value *()*.
+
+```python
+until_3 = unfoldr(lambda acc: ((acc, acc + 1),) if acc < 3 else ())
+print(tuple(until_3(0))) # (0, 1, 2)
+```
+
+### *value*
+
+*value* is simply:
+
+```python
+def value(f: Callable[[Y], Z]) -> Callable[[Tuple[X, Y]], Tuple[X, Z]]:
+ g: Callable[[Tuple[X, Y]], Z] = compose(f, snd)
+ return lambda xy: (fst(xy), g(xy))
+```
+
+Example of *value* usage:
+
+```python
+xys = {"A": [1, 2, 3], "B": [3, 4]}
+xzs = map(value(len), xys.items())
+
+for xz in xzs:
+ print(xz)
+
+# ('A', 3)
+# ('B', 2)
+```
+
+### *where*
+
+*where* is a similar function to *findindex*. The difference between *where* and *findindex* is that *where* returns indices of all elements that match given predicate instead of one. The other difference is that *where* returns a function which takes *Iterable[X]* and returns *Iterable[Y]*, on the other hand *findindex* returns a function which takes *Iterable[X]* and returns *Maybe[int]*.
+
+```python
+xs = where(lambda x: x >= 8)(range(5, 10))
+print(tuple(xs)) # (3, 4)
+
+xs = where(lambda x: x == -1)(range(5, 10))
+print(tuple(xs)) # ()
+```
+
+### *zipflatl*
+
+*zipflatl* is a function which takes a function *Callable[[X], Maybe[Y]]*, and returns some function which takes *Iterable[X]* and returns *Iterable[Tuple[X, Y]]* with only those elements from *Iterable[X]* that are mapped to non-empty *Maybe[Y]* by the function *Callable[[X], Maybe[Y]]*.
+
+```python
+xs = "1", "hello", "2"
+f = try_(int)
+
+ys = zipflatl(f)(xs)
+print(tuple(ys)) # ((1, '1'), (2, '2'))
+```
+
+### *zipflatr*
+
+*zipflatr* is a function which takes a function *Callable[[X], Maybe[Y]]*, and returns some function which takes *Iterable[X]* and returns *Iterable[Tuple[Y, X]]* with only those elements from *Iterable[X]* that are mapped to non-empty *Maybe[Y]* by the function *Callable[[X], Maybe[Y]]*.
+
+```python
+xs = "1", "hello", "2"
+f = try_(int)
+
+ys = zipflatr(f)(xs)
+print(tuple(ys)) # (('1', 1), ('2', 2))
+```
+
+### *zipif*
+
+*zipif* is a function which allows you to zip *Iterable[X]* elements with *Iterable[Y]* elements that match a predicate *Callable[[X, Y], bool]*, using a binary function *Callable[[X, Y], Z]*, into *Iterable[Z]*.
+
+When a pair *x* and *y* do not match the predicate, a function *Callable[[X], Z]* is applied to *x* and its result is yielded. Also, in the next iteration only the first element *x* of the pair will be substituted with it's successor *x'* and *y* will remain unchanged (so that the predicate will get *x'* and *y*).
+
+```python
+participants = (
+ ("Alex", 160.0),
+ ("Sam", 0.0),
+ ("Kate", 150.0),
+ ("John", 155.0),
+ ("Fred", 35.0)
+)
+name = fst
+balance = snd
+
+tickets = (
+ (1, 160),
+ (2, 150),
+ (3, 300)
+)
+ticket_id = fst
+price = snd
+
+sell_tickets = zipif(
+ lambda user, ticket: balance(user) >= price(ticket),
+ lambda user, ticket: (name(user), balance(user) - price(ticket), (ticket,)),
+ lambda user: (*user, ())
+)
+
+for x in sell_tickets(tickets)(participants):
+ print(x)
+
+# ('Alex', 0.0, ((1, 160),))
+# ('Sam', 0.0, ())
+# ('Kate', 0.0, ((2, 150),))
+# ('John', 155.0, ())
+# ('Fred', 35.0, ())
+```
+
+### *zipl*
+
+*zipl* is simply:
+
+```python
+def zipl(xs: Iterable[X]) -> Callable[[Iterable[Y]], Iterable[Tuple[X, Y]]]:
+ return lambda ys: zip(xs, ys)
+```
+
+Example of *zipl* usage:
+
+```python
+xs = "A", "B", "C"
+Pipeline(xs) // zipl(count(1)) * star(flip(repeat)) | print
+
+# A
+# B
+# B
+# C
+# C
+# C
+```
+
+### *zipmapl*
+
+*zipmapl* is simply:
+
+```python
+def zipmapl(f: Callable[[X], Y]) -> Callable[[Iterable[X]], Iterable[Tuple[Y, X]]]:
+ return lambda xs: map(lambda x: (f(x), x), xs)
+```
+
+Example of *zipmapl* usage:
+
+```python
+xs = range(ord("a"), ord("z") + 1)
+upper_to_lower = Pipeline(xs) / chr // zipmapl(str.upper) >> dict
+
+Pipeline(upper_to_lower.items()) // take(5) | print
+
+# ('A', 'a')
+# ('B', 'b')
+# ('C', 'c')
+# ('D', 'd')
+# ('E', 'e')
+```
+
+### *zipmapr*
+
+*zipmapr* is simply:
+
+```python
+def zipmapr(f: Callable[[X], Y]) -> Callable[[Iterable[X]], Iterable[Tuple[X, Y]]]:
+ return lambda xs: map(lambda x: (x, f(x)), xs)
+```
+
+Example of *zipmapr* usage:
+
+```python
+xs = range(ord("a"), ord("z") + 1)
+upper_to_lower = Pipeline(xs) / chr // zipmapr(str.upper) >> dict
+
+Pipeline(upper_to_lower.items()) // take(5) | print
+
+# ('a', 'A')
+# ('b', 'B')
+# ('c', 'C')
+# ('d', 'D')
+# ('e', 'E')
+```
+
+### *zipr*
+
+*zipr* is simply:
+
+```python
+def zipr(ys: Iterable[Y]) -> Callable[[Iterable[X]], Iterable[Tuple[X, Y]]]:
+ return lambda xs: zip(xs, ys)
+```
+
+Example of *zipl* usage:
+
+```python
+xys = {"A": 2.5, "B": 3.14}
+Pipeline(xys.items()) // zipr(count(1)) / flattenl | print
+
+# ('A', 2.5, 1)
+# ('B', 3.14, 2)
+```
+
+
+%prep
+%autosetup -n nonion-0.4.4
+
+%build
+%py3_build
+
+%install
+%py3_install
+install -d -m755 %{buildroot}/%{_pkgdocdir}
+if [ -d doc ]; then cp -arf doc %{buildroot}/%{_pkgdocdir}; fi
+if [ -d docs ]; then cp -arf docs %{buildroot}/%{_pkgdocdir}; fi
+if [ -d example ]; then cp -arf example %{buildroot}/%{_pkgdocdir}; fi
+if [ -d examples ]; then cp -arf examples %{buildroot}/%{_pkgdocdir}; fi
+pushd %{buildroot}
+if [ -d usr/lib ]; then
+ find usr/lib -type f -printf "/%h/%f\n" >> filelist.lst
+fi
+if [ -d usr/lib64 ]; then
+ find usr/lib64 -type f -printf "/%h/%f\n" >> filelist.lst
+fi
+if [ -d usr/bin ]; then
+ find usr/bin -type f -printf "/%h/%f\n" >> filelist.lst
+fi
+if [ -d usr/sbin ]; then
+ find usr/sbin -type f -printf "/%h/%f\n" >> filelist.lst
+fi
+touch doclist.lst
+if [ -d usr/share/man ]; then
+ find usr/share/man -type f -printf "/%h/%f.gz\n" >> doclist.lst
+fi
+popd
+mv %{buildroot}/filelist.lst .
+mv %{buildroot}/doclist.lst .
+
+%files -n python3-nonion -f filelist.lst
+%dir %{python3_sitelib}/*
+
+%files help -f doclist.lst
+%{_docdir}/*
+
+%changelog
+* Wed May 31 2023 Python_Bot <Python_Bot@openeuler.org> - 0.4.4-1
+- Package Spec generated