Allow natural function notations like (1,2) *dot* (3,4) for dot((1,2), (3,4)) or 1 /frac/ 3 for Fraction(1,3), pipes and other useful operators to functions.

## Project description

```a = 2 + (1,2,3) /dot/ (4,5,6)                 # a = 2 + dot((1,2,3), (4,5,6))
Y = [1,2,7,0,2,0] |no_zero |plus(1) |to(set)  # Y == {2,3,8}
square = elipartial(pow, ..., 2)              # square = lambda x: pow(x, 2)
display = hex *compose* ord                   # display = lambda x: hex(ord(x))
```

This example shows how infix operators can be created, the library also introduces bash like pipes and shortcuts to create partial functions or function composition inspired by functional languages.

# Using infix

Infix operators can be created using the `infix` class.

It works for existing functions, like `numpy.dot`:

```import numpy
dot = infix(numpy.dot)
a = (1,2,3) /dot/ (4,5,6)  # use as an infix
```

If you already have `dot` in your namespace, don't worry, it still works as a function:

```a = dot((1,2,3), (4,5,6))  # still works as a function
```

Or for custom functions as a decorator:

```@infix
def crunch(x,y):
"""
Do a super crunchy operation between two numbers.
"""
return x + 2 * y

a = 1 |crunch| 2  # a = crunch(1, 2)
a = crunch(1, 2)  # still works
help(crunch.function)  # to get help about the initial function
```

Any binary operator can be used, `1 |f| 2` can be written `1 *f* 2`, `1 %f% 2` or `1 << f << 2` but `/` or `|` should be clean for all use cases. Beware if you use `**`, the operator is right to left:

```b = 1 **f** 2  # a = f(2, 1)
```

# Useful for dot and cross product

Dot and cross products are used heavily in mathematics and physics as an infix operator `·` or `×`.

```import numpy
dot = infix(numpy.dot)

a = (1,2,3) /dot/ (4,5,6)
a = (1,2,3) |dot| (4,5,6)  # same
r = 2 + (1,2,3) /dot/ (4,5,6)  # here "/" has priority over "+" like in normal python
r = 2 + (1,2,3) *dot* (4,5,6)  # for a dot PRODUCT, "*" seems logical
r = 2 + dot((1,2,3), (4,5,6))  # still works as a function

cross = infix(numpy.cross)
tau = (1,2) /cross/ (3,4)
Z = (1,2,3) /cross/ (4,5,6)
```

# Using `|` for low priority

In some use cases, one want to mix classic operators with function operators, the `|` operator may be used as a low priority operator.

```Y = A + B |dot| C  # is parsed as Y = (A + B) |dot| C
Y = A + B /dot/ C  # is parsed as Y = A + (B /dot/ C)
```

# Useful for fractions

When using the `fractions` module, often you want to transition from `float` to `Fraction`. Your current code uses `/` for division and you can just replace the slashes with `/frac/`, the expression stays natural to read.

```from fractions import Fraction
frac = infix(Fraction)
a = 1 + 1 / 3  # 1.3333...
a = 1 + 1 /frac/ 3  # Fraction(4, 3)
b = 2 * Fraction(a + 3, a + 1)  # very different from '(a + 3) / (a + 1)'
b = 2 * (a + 3) /frac/ (a + 1)  # almost identical to '(a + 3) / (a + 1)'
```

# Useful for ranges, do you like `2..5` in ruby?

In many languages, iterating over a range has a notational shortcut, like `2..5` in ruby. Now you can even write `for i in 1 /inclusive/ 5` in python.

```@infix
def inclusive(a,b):
return range(a, b+1)

for i in 2 /inclusive/ 5:
print(i)  # 2 3 4 5

for i in inclusive(2, 5):
print(i)  # 2 3 4 5
```

However, redefining `range = infix(range)` is a bad idea because it would break code like `isinstance(x, range)`. In that particuliar example, I would choose `exclusive = infix(range)`.

# Useful for isinstance, do you like `instanceof` in Java and Js?

In Java and Javascript, testing the class of an object is done via `x instanceof Class`, the python builtin `isinstance` could be enhanced with infix notation or be renamed to `instanceof`.

```isinstance = infix(isinstance)
assert 1 /isinstance/ int
assert [] /isinstance/ (list, tuple)
assert 1 / 2 |isinstance| float
```

# Useful for pipes: postfix (alias to)

In bash, a functionality called pipes is useful to reuse an expression and change the behavior by just adding code at the end. The library can be used for that.

```@postfix
def no_zero(L):
return [x for x in L if x != 0]

@postfix
def plus_one(L):
return [x+1 for x in L]

Y = [1,2,7,0,2,0] |no_zero |plus_one  # Y == [2,3,8,3]
Y = plus_one(no_zero([1,2,7,0,2,0]))  # Y == [2,3,8,3]
```

Using `to = postfix` makes it quite readable in a pipe.

```>>> from funcoperators import postfix as to  # funcoperators version 0.x
>>> from funcoperators import to             # funcoperators version 1.x
>>> 'hello' |to(str.upper) |to(lambda x:x + '!') |to('I said "{}"'.format) |to(print)
I said "HELLO!"
>>> print('I said "{}"'.format('hello'.upper() + '!'))
I said "HELLO!"
```

# Pipes with arguments: pipe factory

Sometimes, pipes want extra information, for example in our last example, `no_zero` is a special case of a pipe that filters out a value, use the pipe factory recipe like so:

```def filter_out(x):
@postfix
def f(L):
return [y for y in L if y != x]
return f

# shorter with a lambda
def filter_out(x):
return postfix(lambda L:[y for y in L if y != x])

L = [1,2,7,0,2,0] | filter_out(0)  # L == [2,3,8,3]

from funcoperators import mapwith
s = '1 2 7 2'.split() | mapwith(int) | to(sum)  # s = 12 = sum(map(int, '1 2 7 2'.split()))

from funcoperators import mapwith as Map
from funcoperators import filterwith as Filter
S = '1 2 7 2'.split() | Map(int) | Filter(lambda x:x < 5) | to(set)
```

# Useful for format and join

```>>> 42 | format /curryright/ 'x'
'2a'
>>> formatwith = lambda fmt: postfix(lambda value: format(value, fmt))
>>> 52 |formatwith('x')
'2a'
>>> from funcoperators import to
>>> 3.1415 |to('{:.02}'.format)
'3.1'
>>> [1, 2, 7, 2] |mapwith(str) |to('/'.join) |to(print)
1/2/7/2
```

# Function composition (compose, alias circle)

In mathematics and functional programming languages, function composition is naturally used using a `circle` operator to write things like `h = f ∘ g`.

```s = hex(ord('A'))  # s = '0x41'

from funcoperators import compose
display = hex /compose/ ord
s = display('A')  # s = '0x41'

display = hex *circle* ord

from funcoperators import compose as o
display = hex -o- ord  # looks like a dot
```

`compose` can have more than two functions.

```f = compose(str.upper, hex, lambda x:x+1, ord)  # simple function
f = str.upper /compose/ hex /compose/ (lambda x:x+1) /compose/ ord  # operator
```

## Using call for inline compose

```>>> print(5 + 2 * 10, 'B', sep='/')
25
>>> print |call(5 + 2 * 10, 'B', sep='/')
25/B
>>> print(','.join('abcdef' + 3 * 'x'))
a,b,c,d,e,f,x,x,x
>>> print *compose* ' '.join |call('abcdef' + 3 * 'x')
a,b,c,d,e,f,x,x,x
>>> compose(print, ','.join) |call('abcdef' + 3 * 'x')
a,b,c,d,e,f,x,x,x
```
```>>> len |infixcall| 'hallo' * 3
15
```
```>>> print |callargs| ('a', 5)
a 5
```

# More partial syntax

The library adds sugar to functools.partial, using functions called `curry` (and variants like `curryright`, `simplecurry`) and `partially`. The name `curry` comes from other languages.

```def f(x,y,z):
return x + y + z

from funcoperators import curry
g = f /curry/ 5
y = f(2,1)  # y = 8

from funcoperators import curryright
square = pow /curryright/ 2  # square(x) = x ** 2
square = curryright(pow, 2)  # square(x) = x ** 2

from funcoperators import provide_right  # alias provide_right = curryright
square = provide_right(pow, 2)  # square(x) = x ** 2
square = pow /provide_right/ 2  # square(x) = x ** 2

from funcoperators import simplecurry
g = f |simplecurry(1, z=3)
y = g(2)
```

`partially` allows to upgrade a function to provide methods like `f.partial` and provides `f[arg]` to curry.

```from funcoperators import partially
@partially
def f(x,y,z):
return x - y + 2 * z

r = f(1,2,3)
g = f  # g = a function with two arguments: y,z
r = g(2,3)
r = f(2,3)
r = f()
# Notice that "f[1,2]" doesn't work because it gives only one argument: a tuple (@see partiallymulti)

g = f  # gives positional arguments
g = f.val(1)  # gives positional arguments
g = f.key(z=3)  # gives keyword arguments
g = f.partial(1, z=3)  # gives positional and keyword arguments

# alias part = assuming = given = where = partial
g = f.part(1, z=3)
g = f.where(1, z=3)
g = f.given(1, z=3)
```

`partiallymulti` allows `f[arg1, arg2]`.

```from funcoperators import partiallymulti
@partiallymulti
def f(x,y,z):
return x - y + 2 * z

r = f(1,2,3)
g = f[1,2]  # g = a function with one argument: z
r = g(3)
```

# Using partiallyauto

In functional languages, function composition is sometimes not dissociable from function call, `partiallyauto` only works for methods with N fixed positional arguments.

```@partiallyauto
def f(x,y,z):
return x - y + 2 * z

r = f(1,2,3)    # r = 6
r = f(1)(2)(3)  # r = 6
r = f(1)(2,3)   # r = 6
g = f(1)    # g = a function with two arguments
r = g(2,3)  # r = 6
k = g(2)    # k = a function with one argument
```

# Using Ellipsis

Python's `functools.partial` only works for arguments that will be provided later, one must use keywords arguments. However, not all functions accept keywords arguments, like the builtin `pow`, one can use `curryright` because pow only has two arguments.

```square = curryright(pow, 2)  # square(x) = x ** 2
```

The library also proposes to use Python's `...` (`Ellipsis`) as a natural placeholder for arguments. The functions using this convention have a name beginning with `eli`.

```tenexp = elipartial(pow, 10)  # = pow(10, something)
y = tenexp(2)  # 10 ** 2
square = elipartial(pow, ..., 2)  # = pow(something, 2)
y = square(5)  # 5 ** 2
square = pow |elicurry(..., 2)  # = pow(something, 2)
y = square(5)  # 5 ** 2
```

If you like the `partially` and `partiallymulti` syntax, there is `bracket` that has all the concepts in one class.

```@bracket
def f(x,y,z):
return x - y + 2 * z

r = f(1,2,3)
g = f[1, ..., 3]  # g = a function with one argument: y
r = g(2)
g = f.partial(1, ..., 3)  # as a method
g = f.partial(1, z=3)     # allowing keyword arguments
```

Here is a more complex example using `elicurry`, we define `show` to be the `print` function with arguments `1`, something, `3` and keyword argument `sep='/'`.

```show = print |elicurry(1, ..., 3, sep='/')
show(2)  # prints 1/2/3
```

Let's note that `elicurry` has many aliases:

```show = print |elicurry(1, ..., 3, sep='/')
show = print |with_arguments(1, ..., 3, sep='/')
show = print |deferredcall(1, ..., 3, sep='/')
show = print |latercall(1, ..., 3, sep='/')
```

# More examples

See more examples in the test cases in source code.

# Release Notes

Version 1.0 created some non backward-compatible change (`call`) and included useful use cases (`to`, `mapwith`)

## Project details

This version 1.1.3 1.1.2 1.1.1 1.1.0 0.9.6 0.9.5 0.9.4 0.9.3 0.9.2 0.9.1 0.9 0.8.6 0.8.5 0.8.3 0.8.2 0.8.1 0.8 0.7 0.6 0.5 0.4 0.3 0.2