Metadata-Version: 2.1
Name: nonion
Version: 0.3
Summary: Python Functional Programming for Humans.
Home-page: https://bitbucket.org/shkroba/nonion
Author: Illia Shkroba
Author-email: is@pjwstk.edu.pl
License: UNKNOWN
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 consists of two submodules:
        
        * *nonion.tools* - contains a set of functions and types that __might__ simplify your workflow with Functional Programming in Python,
        * *nonion.loader* - contains a wrapper which takes a function *Callable[[io.IOBase], X]* (such as *json.load*), and returns a function *Callable[[typing.Optional[str]], nonion.Option[X]]*.
        
        Also NOnion provides two handful tools:
        
        * *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 core concepts of FP in Python, *NOnion* provides tools that resemble other FP languages tools, but are not exactly the same tools.
        
        ## *nonion.tools*
        
        ### *AnyFunction*
        
        *AnyFunction* is a type alias that describes **any** Python function. *AnyFunction* has following two assumptions:
        
        * *Tuple[object, ...]* - is interpreted as *args*,
        * *Dict[str, object]* - is interpreted as *kwargs*.
        
        *AnyFunction* is defined as follows:
        
        ```python
        AnyFunction = Callable[[Tuple[object, ...], Dict[str, object]], Y]
        ```
        
        ### *Option*
        
        *Option* is a type alias. *Option* resembles Haskell's *Maybe* in Python. *Option* is defined as follows:
        
        ```python
        Option = Union[Tuple[X], Tuple[()]]
        ```
        
        As we can see *Option* is simply some *tuple* that might contain a single value or be an empty *tuple*.
        It means that in order to initialize an *Option* you can simply write:
        
        ```python
        x = () # empty Option
        y = (3,) # Option with value 3
        ```
        
        You can easily check whether an *Option* is empty:
        
        ```python
        def f(x: int) -> Option[int]:
          return (x,) if x < 3 else ()
        
        x: Option[int] = f(5)
        
        if not x:
          print("Option is empty") # Option is empty
        ```
        
        You can also provide an alternative value if *Option* is empty and immediately try to unwrap the *Option*:
        
        ```python
        x: Option[int] = f(5)
        y, *_ = x or (42,)
        
        print(y) # 42
        ```
        
        ```python
        # alternatively
        
        x: Option[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
        ```
        
        If you need to apply some function to a content of the *Option*, you can use *nonion.fmap*:
        
        ```python
        x: Option[int] = f(5)
        z: Option[int] = fmap(lambda y: y + 1, x)
        
        for i in z:
          print(i)
        ```
        
        Because *Option* is simply a *tuple* under the hood, you can apply any Python function (that operates on *tuple*) to an instance of an *Option*.
        
        ### *Either*
        
        *Either* is a type alias. *Either* is defined as follows:
        
        ```python
        Either = Tuple[Option[X], Option[Y]]
        ```
        
        *Either* can be used when you need to return either first value or a second value:
        
        ```python
        def readline(path: str) -> Either[str, str]:
          buffer: Option[IOBase] = wraptry(open)(path)
        
          if not buffer:
            return ((), ("error occurred during open",))
        
          line: Option[str] = fmap(lambda x: x.readline(), buffer)
          fmap(lambda x: x.close(), buffer)
        
          return (line, ())
        
        line, error = readline("requirements.txt")
        
        if line:
          print(*line)
        else:
          print(*error)
        ```
        
        Because *Either* is simply a type alias, it does not checks whether only single value is passed.
        
        ### *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_either*
        
        *as_either* is a function which allows you to create an *Either[Y, X]* from an *Option[X]*. *as_either* takes a function *Callable[[], Y]* and returns a function *Callable[[Option[X]], Either[Y, X]]*.
        
        ```python
        raw_numbers = "1\n22\nten\n333".splitlines()
        
        xs = (
          Pipeline(raw_numbers)
          / wraptry(int)
          / as_either(lambda: f"Failed to parse.")
          & print
        )
        
        # ((), (1,))
        # ((), (22,))
        # (('Failed to parse.',), ())
        # ((), (333,))
        ```
        
        The difference between using *as_either* and explicitly creating *Either* using tuples is that *as_either* will not evaluate the left part if the right part is present. That is why *Callable[[], Y]* is being passed to *as_either* instead of *Y*.
        
        ### *as_match*
        
        *as_match* is simply:
        
        ```python
        def as_match(xys: Iterable[Tuple[X, Y]]) -> Callable[[X], Option[Y]]:
          x_to_y = dict(xys)
        
          def lookup(x: X) -> Option[Y]:
            return (x_to_y[x],) if x in x_to_y else ()
        
          return lookup
        ```
        
        Example of *as_match* usage:
        
        ```python
        successor: Callable[[int], Option[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)
        ```
        
        ### *binary_compose*
        
        *binary_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] = binary_compose(lambda x: x.startswith("a"), second)
        filtered: Iterable[Tuple[int, str]] = filter(p, yxs)
        
        ys = map(first, filtered)
        print(tuple(ys)) # (0, 1)
        ```
        
        ### *bind*
        
        *bind* resembles Haskell's *>>=* in Python.
        
        ```python
        def f(x: int) -> Option[int]:
          return (x + 1,) if x < 3 else ()
        
        x: Option[int] = f(1)
        y: Option[int] = bind(f, x)
        
        print(*y) # 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
        ```
        
        ### *call*
        
        *call* is simply:
        
        ```python
        def call(fx: Tuple[Callable[[Tuple[object, ...]], Y], Tuple[object, ...]]) -> Y:
          f, *x = fx
          return f(*x)
        ```
        
        We assume, that *Tuple[object, ...]* are positional function arguments.
        
        Example of *call* usage:
        
        ```python
        def get_initials(name: str, surname: str) -> str:
          return name[:1] + surname[:1]
        
        names = "Haskell Curry", "John Smith", "George Sand"
        
        (
          Pipeline(names)
          // zipl(repeat(get_initials))
          / key(star)
          / value(lambda x: x.split(" "))
          / call
          & print
        )
        
        # HC
        # JS
        # GS
        ```
        
        ### *catch*
        
        *catch* is a function that resembles pattern-matching in Python. It takes some functions `*fs: Callable[[X], Option[Y]]` with some catch-all function `default: Callable[[X], Y]` and returns a function `Callable[[X], Y]` which executes `fs` functions one by one until some function will return non-empty `Option[Y]`. If none of those functions will return a non-empty `Option[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(
          wraptry(parse_range),
          wraptry(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)
        ```
        
        ### *curry*
        
        *curry* is simply:
        
        ```python
        def curry(f: AnyFunction[Y]) -> AnyFunction[Y]:
          return lambda *args, **kwargs: partial(f, *args, **kwargs)
        ```
        
        ### *drop*
        
        *drop* is simply:
        
        ```python
        def drop(n: int) -> Callable[[Iterable[X]], Iterable[X]]:
          return lambda xs: islice(xs, n, None)
        ```
        
        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)
        ```
        
        ### *find*
        
        *find* is a function which takes a predicate and returns a function which takes some *Iterable* and returns an *Option* with value that matches the predicate if such value exists:
        
        ```python
        x: Option[int] = find(lambda x: x == 3)(range(5))
        print(x) # (3,)
        
        x: Option[int] = find(lambda x: x == -1)(range(5))
        print(x) # ()
        ```
        
        ### *find_and_collect*
        
        *find_and_collect* is a function which takes a predicate, some *Iterator* and a buffer, and returns an *Option* and passed buffer.
        The *Option* contains a value that matches the predicate if such value exists. The buffer contains values that were checked using the predicate:
        
        ```python
        buffer = []
        xs = iter(range(5))
        x, filled_buffer = find_and_collect(lambda x: x == 3, xs, buffer)
        
        print(x) # (3,)
        print(filled_buffer) # [0, 1, 2, 3]
        
        # notice: Iterator has to be passed, not Iterable
        
        buffer = []
        x, filled_buffer = find_and_collect(lambda x: x == 3, range(5), buffer)
        
        print(x) # ()
        print(filled_buffer) # []
        ```
        
        ### *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: Option[int] = findindex(lambda x: x == 8)(range(5, 10))
        print(x) # (3,)
        
        x: Option[int] = findindex(lambda x: x == -1)(range(5, 10))
        print(x) # ()
        ```
        
        ### *first*
        
        *first* is simply:
        
        ```python
        def first(xy: Tuple[X, Y]) -> X:
          return xy[0]
        ```
        
        ### *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)
        ```
        
        ### *fleft*
        
        *fleft* is a function which allows you to flatten an *Either* which contains another *Either* as its right value. It is simply defined as:
        
        ```python
        @curry
        def fleft(f: Callable[[Z], X], xzy: Either[X, Either[Z, Y]]) -> Either[X, Y]:
          x, zy = xzy
        
          if x:
            return x, ()
          else:
            (z, y), *_ = zy
            return fmap(f, z), y
        ```
        
        Example of *fleft* usage:
        
        ```python
        def drive(x: int) -> Either[str, str]:
          if x < 18: return ("You are too young to drive.",), ()
          else: return (), (("OK",),)
        
        f: Callable[[str], Either[str, Tuple[()]]] = lambda x: (
          Pipeline(wrapexcept(int)(x))
          // left(lambda y: f"Parsing error: {y}")
          // right(drive)
          // fleft(lambda y: f"Drive error: {y}")
          >> tuple
        )
        
        print(f("eighteen")) # (("Parsing error: invalid literal for int() with base 10: 'eighteen'",), ())
        
        print(f("17")) # (('Drive error: You are too young to drive.',), ())
        
        print(f("25")) # ((), (('OK',),))
        ```
        
        ### *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
        ```
        
        ### *fmap*
        
        *fmap* resembles Haskell's *fmap* in Python. It is intended to be used with *Option*, because it transforms the result of Python's *map* function into *tuple*. *fmap* is defined as follows:
        
        ```python
        def fmap(f: Callable[[X], Y], x: Iterable[X]) -> Tuple[Y, ...]:
          return binary_compose(tuple, lift(f))(x)
        ```
        
        If you simply want to lift some function without composing the resulting function with a *tuple*, use a *lift* function.
        
        ```python
        def f(x: int) -> Option[int]:
          return (x + 1,) if x < 3 else ()
        
        x: Option[int] = f(1)
        y: Option[int] = fmap(lambda x: x + 5, x)
        
        print(*y) # 7
        ```
        
        ### *fold*
        
        *fold* is simply:
        
        ```python
        def fold(f: Callable[[Y, X], Y], acc: Y) -> Callable[[Iterable[X]], Y]:
          return lambda xs: reduce(f, xs, acc)
        ```
        
        It is a convenience function which takes only swapped second and third arguments of Python's *reduce*
        function. The first argument of *reduce* function has to be supplied to returned function.
        It makes it easy to partially apply some function and accumulator.
        
        Example of *fold* usage:
        
        ```python
        xs = range(ord("A"), ord("Z") + 1)
        alphabet = Pipeline(xs) / chr >> fold(operator.add, "")
        
        print(alphabet)
        
        # ABCDEFGHIJKLMNOPQRSTUVWXYZ
        ```
        
        ### *fold1*
        
        *fold1* is a strict version of *fold*. It is simply defined as:
        
        ```python
        def fold1(f: Callable[[Y, X], Y]) -> Callable[[Iterable[X]], Y]:
          return lambda xs: reduce(f, xs)
        ```
        
        *fold1* resulting function will raise an error if supplied *Iterable[X]* is empty.
        
        ### *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: second(x) == second(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, second))(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
        ```
        
        ### *iterfind*
        
        *iterfind* is a function which takes an *Iterable* of predicates and returns a function which takes some *Iterable* and returns an *Iterable* of *Option*. Each *Option*
        contains a matched value of a corresponding predicate. *iterfind* uses *find_and_collect* under the hood.
        *iterfind* firstly searches for matching value in a buffer, if it could not find one, it passes predicate along with buffer to *find_and_collect*.
        
        ```python
        fs = (lambda x: x == 2), (lambda x: x == 4), (lambda x: x == 1), (lambda x: x == -1)
        ys: Iterable[Option[int]] = iterfind(fs)(range(5))
        
        for y in ys:
          print(y)
        
        # (2,)
        # (4,)
        # (1,)
        # ()
        ```
        
        ### *key*
        
        *key* is simply:
        
        ```python
        def key(f: Callable[[X], Z]) -> Callable[[Tuple[X, Y]], Tuple[Z, Y]]:
          g: Callable[[Tuple[X, Y]], Z] = binary_compose(f, first)
          return lambda xy: (g(xy), second(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])
        ```
        
        ### *left*
        
        *left* is simply:
        
        ```python
        @curry
        def left(f: Callable[[X], Z], xy: Either[X, Y]) -> Either[Z, Y]:
          x, y = xy
          return fmap(f, x), y
        ```
        
        ### *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)
        ```
        
        ### *maptry*
        
        *maptry* is a function which takes a function *Callable[[X], Y]*, and returns some function which takes *Iterable[X]* and returns *Iterable[Y]*. If a function *Callable[[X], Y]* will raise an error for some element, that element will be omitted.
        
        ```python
        possible_jsons = "{}", "", "123, 32323", "{\"a\": 1}"
        jsons = maptry(json.loads)(possible_jsons)
        
        for x in jsons:
          print(x)
        
        # {}
        # {'a': 1}
        ```
        
        ### *match*
        
        *match* is a function that resembles pattern-matching in Python. It takes some functions `*fs: Callable[[X], Option[Y]]` and returns a function `Callable[[X], Option[Y]]` which executes `fs` functions one by one until some function will return non-empty `Option[Y]`. If none of those functions will return a non-empty `Option[Y]`, an empty `Option[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(
          wraptry(parse_range),
          wraptry(parse_unbounded_range)
        )
        
        for x in age_ranges:
          print(parse(x))
        
        # ((10, 20),)
        # ((20, 30),)
        # ((30, 100),)
        # ((60, 100),)
        # ()
        ```
        
        ### *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.
        
        ### *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)
        ```
        
        ### *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))
        ```
        
        ### *right*
        
        *right* is simply:
        
        ```python
        @curry
        def right(f: Callable[[Y], Z], xy: Either[X, Y]) -> Either[X, Z]:
          x, y = xy
          return x, fmap(f, y)
        ```
        
        ### *second*
        
        *second* is simply:
        
        ```python
        def second(xy: Tuple[X, Y]) -> Y:
          return xy[1]
        ```
        
        ### *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 **strict=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, strict=True)(range(10))
        print(tuple(xs))
        # ((0, 1, 2), (2, 3, 4), (4, 5, 6), (6, 7, 8))
        ```
        
        ### *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)
        ```
        
        ### *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: second(x) == second(y))(people)
        print(tuple(stripped))
        # (('Alex', 23), ('Sam', 27), ('Fred', 23))
        
        # or you can use *on* function:
        
        stripped = stripby(on(operator.eq, second))(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)
        ```
        
        Example of *take* usage:
        
        ```python
        xs = take(1)(range(3))
        print(tuple(xs)) # (0,)
        
        xs = islice(range(3), 1)
        print(tuple(xs)) # (0,)
        ```
        
        ### *tee*
        
        *tee* 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 = tee(print)(5) # 5
        print(x) # 5
        
        xs = (
          Pipeline(range(3, 0, -1))
          / tee(print, "Countdown:", file=sys.stderr)
          >> tuple
        )
        # Countdown: 3
        # Countdown: 2
        # Countdown: 1
        
        print(xs) # (3, 2, 1)
        ```
        
        ### *value*
        
        *value* is simply:
        
        ```python
        def value(f: Callable[[Y], Z]) -> Callable[[Tuple[X, Y]], Tuple[X, Z]]:
          g: Callable[[Tuple[X, Y]], Z] = binary_compose(f, second)
          return lambda xy: (first(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)
        ```
        
        ### *wrapeek*
        
        *wrapeek* is a function which takes an *Iterable* and returns an *Option* containing a first value of the *Iterable* along with an original *Iterable* (containing first value).
        
        ```python
        xs = (x for x in range(5))
        x, ys = wrapeek(xs)
        
        print(x) # (0,)
        
        for y in ys:
          print(y)
        
        # 0
        # 1
        # 2
        # 3
        # 4
        ```
        
        ### *wrapexcept*
        
        *wrapexcept* is a decorator which returns a function that returns *Either* with some value or an *Exception* that was raised.
        
        ```python
        f = wrapexcept(next)
        xs = iter(range(2))
        
        print(f(xs)) # ((), (0,))
        print(f(xs)) # ((), (1,))
        print(f(xs)) # ((StopIteration(),), ())
        ```
        
        ### *wrapnext*
        
        *wrapnext* is simply:
        
        ```python
        wrapnext: Callable[[Iterator[X]], Option[X]] = wraptry(next)
        ```
        
        Example of *wrapnext* usage:
        
        ```python
        xs = iter(range(2))
        
        print(wrapnext(xs)) # (0,)
        print(wrapnext(xs)) # (1,)
        print(wrapnext(xs)) # ()
        ```
        
        ### *wraptry*
        
        *wraptry* is a decorator which returns a function that returns *Option* with some value or an empty *Option* if an *Exception* was raised.
        
        ```python
        load_json = wraptry(json.loads)
        
        print(load_json("{}")) # ({},)
        print(load_json("[1, 2, 3]")) # ([1, 2, 3],)
        print(load_json("abc")) # ()
        ```
        
        ### *zipflatl*
        
        *zipflatl* is a function which takes a function *Callable[[X], Option[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 *Option[Y]* by the function *Callable[[X], Option[Y]]*.
        
        ```python
        xs = "1", "hello", "2"
        f = wraptry(int)
        
        ys = zipflatl(f)(xs)
        print(tuple(ys)) # ((1, '1'), (2, '2'))
        ```
        
        ### *zipflatr*
        
        *zipflatr* is a function which takes a function *Callable[[X], Option[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 *Option[Y]* by the function *Callable[[X], Option[Y]]*.
        
        ```python
        xs = "1", "hello", "2"
        f = wraptry(int)
        
        ys = zipflatr(f)(xs)
        print(tuple(ys)) # (('1', 1), ('2', 2))
        ```
        
        ### *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)
        ```
        
        ## *nonion.loader*
        
        ### *FROM_STDIN*
        
        *FROM_STDIN* is *None*. *FROM_STDIN* is defined for readability purposes. When you write CLI which can read users input from **stdin** by default, you can use this constant instead of using *None*.
        
        ### *as_loader*
        
        *as_loader* is a decorator which takes a *BufferLoader* and creates a *Loader*. *BufferLoader* and *Loader* are defined as follows:
        
        ```python
        BufferLoader = Callable[[IOBase, Tuple[object, ...], Dict[str, object]], X]
        Loader = Callable[[Optional[str], Tuple[object, ...], Dict[str, object]], Option[X]]
        ```
        
        For example, **json.load** and **pd.read_csv** are *BufferLoader*'s.
        
        Created *Loader* will take a path as its first argument and will read the content using Python built-in *open*. If path is not provided, *Loader* reads content from **stdin**. If during read or during *BufferLoader* call exception raises, *Loader* will return an empty *Option*.
        
        ```python
        # first_column_extractor.py
        from typing import Callable, Optional
        
        import pandas as pd
        
        from nonion import Option
        from nonion import as_loader
        from nonion import bind
        from nonion import fmap
        from nonion import wraptry
        
        load_frame = as_loader(pd.read_csv)
        frame: Option[pd.DataFrame] = load_frame()
        
        get_first_column = wraptry(lambda x: x.iloc[:, 0])
        # x.iloc[:, 0] might raise an error, so use wraptry
        
        series: Option[pd.Series] = bind(get_first_column, frame)
        
        to_csv = lambda x: x.to_csv(header=False, index=False)
        raw_series: Option[str] = fmap(to_csv, series)
        
        if not series:
          print("something went wrong")
        else:
          print(*raw_series, end="")
        ```
        
        We can use script *first_column_extractor.py* in a following way in a Bash-like shells:
        
        ```bash
        python first_column_extractor.py < frame.csv
        ```
        
        ### *load*
        
        *load* is a function which takes an *Optional* path to a file and returns an *IOBase* buffer containing content of the file. If path does not exists *load* uses **stdin**.
        
        ```bash
        with load() as buffer:
          print(buffer.read())
        ```
        
        Notice: when *load* uses **stdin**, it firstly reads whole **stdin** content.
        
        ### *load_json*
        
        *load_json* is simply:
        
        ```python
        load_json: Loader[Union[Dict[str, object], Tuple[object, ...]]] = as_loader(json.load)
        ```
        
        Example of *load_json* usage:
        
        ```python
        x = load_json("object.json")
        print(x) # ([1, 2, 3],)
        ```
        
        ### *wrapopen*
        
        *wrapopen* is simply:
        
        ```python
        wrapopen: Callable[[str], Option[IOBase]] = wraptry(open)
        ```
        
        Example of *wrapopen* usage:
        
        ```python
        x = wrapopen("missing_object.json")
        print(x) # ()
        ```
        
        ## *Function*
        
        In order to create a *Function*, you simply pass any *Callable*:
        
        ```python
        f = Function(lambda x: x + 1)
        f(5) # returns 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) # returns 12
        ```
        
        The *@* (at) operator was used, because it reminds $\circ$ symbol.
        
        ### *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) # returns 11
        
        # alternatively
        
        g = Function().then(lambda x: x * 2).then(f)
        g(5) # returns 11
        ```
        
        The */* (slash) operator was used, because it reminds *|* (vertical bar) used for piping.
        
        ### *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) # returns 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 # returns 11
        ```
        
        The *&* (ampersand) operator was used, because it looks similar to *$* (dollar), which is a Haskell operator.
        
        ### *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) # returns 5
        ```
        
        *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
        #
        ```
        
        The *\** (star) operator was used, because instead of passing an *Iterable* to a function, you pass its content as with Python *\** (star) operator and functions that take *\*args*.
        
        ## *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))
        ```
        
        The */* (slash) operator was used, because it reminds *|* (vertical bar) used for piping.
        
        ### *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))
        ```
        
        The *\** (star) operator was used, because intuitively you use Python *\** (star) operator on each result.
        
        ### *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) >> wrapnext
        print(ys) # (1,)
        
        ys = Pipeline(range(2)) % (lambda x: x == 5) >> wrapnext
        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))
        ```
        
Platform: UNKNOWN
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3.6
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
Classifier: Typing :: Typed
Requires-Python: >=3.6
Description-Content-Type: text/markdown
