# forbiddenfp

## Summary

Functional-Programming (FP) in a forbidden way:

Turn arbitrary function into postfix notation in favor of function chaining.

And the library provides many useful patches for builtin/itertools functions.

## Install

Compatible with Python 3.7+

```shell
pip install forbiddenfp
```

## Examples

```python
# objects are already patched at import time
import forbiddenfp

"abc".print().len()  # print out "abc", then return 3
"abc".then(lambda s: s * 2).filter(lambda s: s == "b").join()  # "bb"

# A more complex one (examples/word_count.py)
("./lorem_ipsum.txt"
 .with_context(open, lambda path, f: f.read().also(print(f"Reading {path}")))
 .then(lambda s: s.split(" "))
 .counter()
 .print())

```

See more `./examples`.

## Why Functional Programming

Separate out control structs (which are functions provided by library) from business logic (which are lambda functions
supplied to fill the control structs).

So we can have a clearer scope, and separate concerns, when we want to change on either side of things.

- `if ...` and `if ... else ...` are modeled by Maybe/Either monads.
- `while ...` and `for ... in ...` are generator/iterators. Additionally, stop-early behavior is `takewhile` of the sequence.

Philosophically, think more in `def transform(old_state) -> new_state`, rather than `state = modify(state)`.

## Warning

This library patches builtin `object` (and hence ALL classes),
with hacks around CPython API (provided by [forbiddenfruit](https://github.com/clarete/forbiddenfruit)),

so consider this NSFW (Not Safe For Work).

## Known Issues

`None` doesn't work well with chained keyword arguments.

```python
import forbiddenfp

None.apply(print)  # works
None.apply(func=print)  # doesn't work
```
