diff options
author | CoprDistGit <infra@openeuler.org> | 2023-05-31 07:18:14 +0000 |
---|---|---|
committer | CoprDistGit <infra@openeuler.org> | 2023-05-31 07:18:14 +0000 |
commit | 31bbf4cf31281be2909f0519d4d2b45af659f9ea (patch) | |
tree | 379338282982eade7aef46d56c79166c696de805 | |
parent | 7d19577fab944240449823c0cebddd1518bf22d7 (diff) |
automatic import of python-nonion
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | python-nonion.spec | 5046 | ||||
-rw-r--r-- | sources | 1 |
3 files changed, 5048 insertions, 0 deletions
@@ -0,0 +1 @@ +/nonion-0.4.4.tar.gz 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 @@ -0,0 +1 @@ +d7ec4dd056fd3824691f352023db4079 nonion-0.4.4.tar.gz |