# Tutorial¶

“Divide et impera.”

## Function Creation¶

`Function` is a function/callable wrapper.

```
>>> from fx import Function
>>> length = Function(len)
>>> length([1, 2, 3])
3
```

Alias `f` can be used instead,
for convenience and succinctness.

```
>>> from fx import f
>>> length = f(len)
>>> length(range(5))
5
```

When used on a non-callable object,
the newly created `Function` instance
will return the same object when called as a function.

```
>>> the_answer = f(42)
>>> the_answer()
42
```

## Function Invocation¶

Calling method `invoke()` on a `Function` instance
will invoke the wrapped function with supplied arguments.

```
>>> minus = f(lambda a, b: a - b)
>>> minus.invoke(5, 2)
3
```

`call()` is an alias to `invoke()`.

```
>>> minus.call(5, 2)
3
```

`Function` overloads `__call__()`,
which means, instance of `Function` can be invoked like a normal function.

```
>>> minus(3, 2)
1
```

Keyword arguments are supported as well.

```
>>> minus.invoke(b=2, a=3)
1
>>> minus.call(b=2, a=3)
1
>>> minus(b=2, a=3)
1
```

`Function` overloads `__pos__()`,
which implements unary operator `+`,
when used, calls `invoke()` with no arguments.

```
>>> lst = f(list)
>>> lst()
[]
>>> +lst
[]
>>> lst() == +lst
True
```

`value` is a read-only property, when accessed,
calls `invoke()` with no arguments.

```
>>> lst(range(3))
[0, 1, 2]
>>> lst.value
[]
```

## Function Application¶

Partial function application can be done with method `apply()`.

```
>>> five_minus = minus.apply(5)
>>> five_minus(2)
3
>>> five_minus_four = five_minus.apply(4)
>>> five_minus_four.value
1
```

`apply()` accepts arbitrary arguments that wrapped function accepts.

```
>>> five_minus_four = minus.apply(5, 4)
>>> five_minus_four.value
1
>>> five_minus_four = minus.apply(b=4, a=5)
>>> five_minus_four.value
1
```

Operator `<<` is overloaded as function application operator,
so the above code can be rewritten with `<<` like this.

```
>>> five_minus = minus << 5
>>> five_minus(2)
3
>>> five_minus_four = five_minus << 4
>>> five_minus_four.value
1
>>> five_minus_four = minus << 5 << 4
>>> five_minus_four.value
1
```

`<<=` works as well.

```
>>> m = minus
>>> m <<= 5
>>> m <<= 4
>>> m()
1
>>> m.value
1
```

Operator `&` is overloaded as function application operator, too.

```
>>> five_minus = minus & 5
>>> five_minus(2)
3
>>> five_minus_four = five_minus & 4
>>> five_minus_four.value
1
>>> five_minus_four = minus & 5 & 4
>>> five_minus_four.value
1
>>> m = minus
>>> m &= 5
>>> m &= 4
>>> m()
1
>>> m.value
1
```

Why do we need two different operators doing seemingly the same thing? It is because they have different precedence, and that helps.

Consider this scenario,
we want to do something to each element in a sequence,
one way to do it is using `map` maps a function over this sequence.

```
>>> seq = [1, 3, 5, 7, 9]
>>> list(map(str, seq))
['1', '3', '5', '7', '9']
```

With partial function application, even functions require more than one arguments can be used to map over a single sequence, for example, we can double every element in this way.

```
>>> mul = f(lambda a, b: a * b)
>>> double = mul << 2
>>> list(map(double, seq))
[2, 6, 10, 14, 18]
```

Instead of hard-coding `seq` here,
we can use partial function application technique again,
creating a function that can be re-used over and over again.

```
>>> double_all = f(map) << double
>>> list(double_all(seq))
[2, 6, 10, 14, 18]
>>> list(double_all(range(5)))
[0, 2, 4, 6, 8]
>>> list(double_all('Hello'))
['HH', 'ee', 'll', 'll', 'oo']
```

If we don’t need all these intermediate functions,
`double_all` can be coded in one line.

```
>>> double_all = f(map) << (f(lambda a, b: a * b) << 2)
>>> list(double_all(seq))
[2, 6, 10, 14, 18]
```

This is where operator `&` comes in handy,
by using both function application operators,
we can eliminate some parentheses.

```
>>> double_all = f(map) & f(lambda a, b: a * b) << 2
>>> list(double_all(seq))
[2, 6, 10, 14, 18]
```

## Function Composition¶

In the above example,
we have to wrap the result of `map` with a list constructor `list`
just to make sure the result will be the same in Python 2.x and Python 3.x,
because this is one place where Python 2.x and Python 3.x differ.

```
>>> double_all = f(map) & f(lambda a, b: a * b) << 2
>>> list(double_all(seq))
[2, 6, 10, 14, 18]
```

But typing all these `list(` and `)` is no fun,
there is a way to avoid this,
we can compose `double_all` with `list`.

```
>>> new_double_all = f(list).compose(double_all)
>>> new_double_all(seq)
[2, 6, 10, 14, 18]
```

`**` is the function composition operator,
keep in mind that this operator is **right-associative**,
just like the function composition operator `(.)` in Haskell.

```
>>> new_double_all = f(list) ** double_all
>>> new_double_all(seq)
[2, 6, 10, 14, 18]
```

Because both `__pow__()` and `__rpow__()` are implemented,
of the two operants of operator `**`,
one instance of `Function` will suffice to make it work.

Since `double_all` is already an instance of `Function`,
there is no need to wrap `list` in a `Function`.

```
>>> new_double_all = list ** double_all
>>> new_double_all(seq)
[2, 6, 10, 14, 18]
```

With function composition operator `**`,
it is possible to refine `double_all` into a one-liner.

```
>>> double_all = list ** f(map) & f(lambda a, b: a * b) << 2
>>> double_all(seq)
[2, 6, 10, 14, 18]
```

Here is a more complicated example.

```
>>> from itertools import count, takewhile as tw
>>> takewhile = f(tw)
>>> select = f(filter)
>>> odd = lambda n: n % 2
>>> lt_20 = lambda n: n < 20
>>> reverse = lambda s: s[::-1]
>>> + reverse ** list ** (select << odd) ** (takewhile << lt_20) ** count
[19, 17, 15, 13, 11, 9, 7, 5, 3, 1]
```

It’s easier to read function composition expressions from right to left:

From all whole numbers (count), we keep taking numbers as long as it is less than 20 (takewhile << lt_20), pick all odd numbers from the resulting sequence (select << odd), make it into a list (list), reverse it (reverse), and get the result (+).

Warning

Coding in this style is fun, but tend to get hairy soon.
*Don’t Try This at Home.*

## Function Pipeline¶

When called with a callable,
method `pipe()` will return an instance of `Function`,
which when invoked,
will pipe the output of current function into that callable.
It works very similarly to pipelines in Unix-like systems,
thus the name.

To put it simply, piping the output of functions does the same thing as function composition, just in reversed direction, that is, it is evaluated from left to right.

Remember the examples from last section?

```
>>> double_all = f(map) & f(lambda a, b: a * b) << 2
>>> list(double_all(seq))
[2, 6, 10, 14, 18]
>>> new_double_all = f(list).compose(double_all)
>>> new_double_all(seq)
[2, 6, 10, 14, 18]
```

Rewriting `new_double_all` with `pipe()`.

```
>>> new_double_all = double_all.pipe(list)
>>> new_double_all(seq)
[2, 6, 10, 14, 18]
```

Like in a Unix shell, we can use `|` as pipe operator.

```
>>> new_double_all = double_all | list
>>> new_double_all(seq)
[2, 6, 10, 14, 18]
```

Both `__or__()` and `__ror__()` are implemented,
so only one of the two operants needs to be an instance of `Function` to make it work.

Let’s take a look at the last example of last section, again.

```
>>> from itertools import count, takewhile as tw
>>> takewhile = f(tw)
>>> select = f(filter)
>>> odd = lambda n: n % 2
>>> lt_20 = lambda n: n < 20
>>> reverse = lambda s: s[::-1]
>>> + reverse ** list ** (select << odd) ** (takewhile << lt_20) ** count
[19, 17, 15, 13, 11, 9, 7, 5, 3, 1]
```

The following proves that function pipeline is equivalent to function composition in reversed direction.

```
>>> s = count | (takewhile << lt_20) | (select << odd) | list | reverse
>>> +s
[19, 17, 15, 13, 11, 9, 7, 5, 3, 1]
```

Since operator `<<` has higher precedence than `|`,
parentheses can often be omitted.

```
>>> s = count | takewhile << lt_20 | select << odd | list | reverse
>>> +s
[19, 17, 15, 13, 11, 9, 7, 5, 3, 1]
```

## Reversed Function Application¶

It is sometimes convenient to reverse the expected order of arguments,
method `reverse_apply()` helps in this situation.

It returns an instance of `Function`,
which takes arguments like the original one,
but in reversed order.

```
>>> minus = f(lambda a, b: a - b)
>>> minus(2, 1)
1
>>> subtract = minus.reverse_apply()
>>> subtract(2, 1)
-1
```

You can get the ‘flipped’ function via read-only property `flip`, too.
It’s named after Haskell’s `flip` function.

```
>>> subtract = minus.flip
>>> subtract(2, 1)
-1
>>> minus.flip(2, 1)
-1
```

Flipping a ‘flipped’ function again will cancel each other out.

```
>>> minus(2, 1)
1
>>> minus.flip(2, 1)
-1
>>> minus.flip.flip(2, 1)
1
>>> minus.flip.flip.flip(2, 1)
-1
>>> minus.flip.flip.flip.flip(2, 1)
1
```

The flip operator `~` does the same thing.

```
>>> minus(2, 1)
1
>>> (~minus)(2, 1)
-1
>>> (~~minus)(2, 1)
1
>>> (~~~minus)(2, 1)
-1
>>> (~~~~minus)(2, 1)
1
```

## Implicit Function Invocation¶

Operators `!=` and `==` are overloaded,
so that when using these two operators to compare anything to an instance of `Function`,
it is equivalent to compare against that instance’s `value`.

For example:

```
>>> s = f(range) | list
>>> (s << 3).value
[0, 1, 2]
>>> s << 3 == [0, 1, 2]
True
>>> s << 3 != [0, 1, 2]
False
>>> f(range) << 3 | list == [0, 1, 2]
True
>>> [0, 1, 2] != f(range) << 3 | list
False
>>> f(range) << 3 | list == f(range) << 3 | list
True
>>> f(range) << 3 | list != s << 3
False
```

We can test if a value is in a `Function`‘s output,
in the form of `value in function`.

For `Function` that its `value` supports membership test operator `in`,
(either by supporting the iterator protocol or implementing it’s `__contains__` method),
membership testing will be delegated to its `value`.

```
>>> one_to_ten = list ** f(range) << 1 << 11
>>> 1 in one_to_ten
True
>>> 10 in one_to_ten
True
>>> 11 in one_to_ten
False
>>> one_to_ten.value
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
```

For `Function` that its `value` does not support membership test operator `in`,
equality is checked instead.

```
>>> the_answer = len ** f(range) << 42
>>> 42 in the_answer
True
>>> 41 in the_answer
False
>>> the_answer()
42
```

Instances of `Function` support iterator protocol.

For `Function` which its `value` is an iterable object,
iteration is delegated to that object.

```
>>> one_to_three = f(range) << 1 << 4
>>> for i in one_to_three:
... print(i)
1
2
3
>>> [i * 2 for i in one_to_three]
[2, 4, 6]
```

For `Function` which its `value` is not an iterable,
a 1-tuple with `value` as the only element will be used to iterated over.

```
>>> the_answer = f(42)
>>> the_answer()
42
>>> [i for i in the_answer]
[42]
```

Warning

If you don’t know how these features work, as described in this section,
they might lead to surprising results and possibly cause more problems than they solve.
*You’ve been warned.*

## Overloaded Operators¶

In summary, `Function` overloads the following operators.

## Item Getter¶

There is a factory object `x`, that creates item getters.

Note

This is different from standard library’s

operator.itemgetter.

Creating an index getter:

```
>>> from fx import x
>>> first = x[0]
>>> first([1, 2, 3])
1
```

Keys can be `slice` as well:

```
>>> rest = x[1:]
>>> rest([1, 2, 3])
[2, 3]
```

Item getters can be chained:

```
>>> second = rest[0]
>>> second([1, 2, 3])
2
>>> third = x[1:][1:][0]
>>> third([1, 2, 3])
3
```

Keys are not limited to numbers:

```
>>> get_name = x['name']
>>> get_name({'name': 'Joe', 'age': 42})
'Joe'
```

```
>>> from fx import _
>>> age = _['age']
>>> age({'name': 'Joe', 'age': 42})
42
```

Item getters work on generators as well:

```
>>> def monty():
... yield 'spam'
... yield 'ham'
... yield 'eggs'
>>> first(monty())
'spam'
>>> second(monty())
'ham'
>>> third(monty())
'eggs'
>>> odd_indices = x[::2]
>>> list(odd_indices(monty()))
['spam', 'eggs']
```

Item getters is non-strict, e.g, work on infinite sequence:

```
>>> from itertools import count
>>> first(count(1))
1
>>> second(count(1))
2
>>> third(count(1))
3
```

Using itemgetter in pipeline:

```
>>> def fibonacci():
... """Fibonacci sequence starts with 1, 1"""
... a, b = 0, 1
... while True:
... a, b = b, a + b
... yield a
>>> fib = fibonacci()
>>> f(fib) | _[:8] | list == [1, 1, 2, 3, 5, 8, 13, 21]
True
```

## Utility Functions¶

Package `fx` also provides a couple utility functions, `compose()` and `flip()`.

They work like `Function`‘s methods with the same name,
except that two functions instead of one are required because there is no `self`.

```
>>> from fx import compose
>>> g = compose(lambda n: -n, abs)
>>> g(-1)
-1
```

```
>>> from fx import flip
>>> greater_then = lambda a, b: a > b
>>> greater_then(1, 2)
False
>>> less_then = flip(greater_then)
>>> less_then(1, 2)
True
```