Metadata-Version: 2.1
Name: funext
Version: 0.1.0
Summary: Functional extensions for Python objects
Home-page: https://github.com/ReinhardtJ/Functional-Extensions
Author: Jonas Reinhardt
Author-email: jonas@reinhardt.ai
License: UNKNOWN
Project-URL: Bug Tracker, https://github.com/ReinhardtJ/Functional-Extensions/issues
Platform: UNKNOWN
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Requires-Python: >=3.7
Description-Content-Type: text/markdown

# Functional Extensions
for Python

This repository aims to extend the Python programming language by adding some
known concepts from other popular programming languages. Most of those concepts
are known from languages that support functional programming.

- being able to chain pure (and mutating) functions by calling them directly 
  on objects. Most of those functions are known from the built-in functions.

- applying functions to objects and chaining them through a pipe-function
- composing functions (not yet implemented)

## Usage

```python
from functional_extensions import l_, s_, d_, t_, f_

regular_list = [1, 2, 3, 4]
extended_list = l_(1, 2, 3, 4)

# all extended classes have a simple initializer function. Containers can either
# take individual values as *args, or a regular collection
extended_list = l_([1, 2, 3, 4]) # or
extended_list = l_(1, 2, 3, 4)

extended_set = s_(1, 2, 3, 4) # or
extended_set = s_(set(1, 2, 3, 4))

extended_dict = d_({'key': 'value'})

extended_tuple = t_(1, 2, 3, 4) # or
extended_tuple = t_((1, 2, 3, 4))

def add(a, b): return a + b
extended_function = f_(add)
```
## Available functions
### Object


```python
pipe_(self, function, *args, **kwargs)
``` 

Applies the object `self` to a function `function` with the arguments from `*args` 
and `**kwarngs` and returns the result.

---

```python
copy_(self) 
deepcopy_(self)
``` 

Copies `self`, either shallowly or deeply, and returns the result.

---

```python
type_(self)
```

Returns `type(self)`.

### Sequences and Containers

#### Iterable
```python
to_list_(self)
to_set_(self)
to_tuple_(self)
```

Converts the iterable `self` into the desired object.

---

```python
map_(self, function, *args, **kwargs)
```

Applies every element of `self` to `function` and returns an iterable of the new
elements. Passes `*args` and `**kwargs` to the map-`function`.

Returns an object of the same type as that of `self`.

---

```python
for_each_(self, apply, *args, **kwargs)
```

For every element in `self`, `function` is called with said element, as well
as `*args` and `**kwargs`. The list is then returned.

---

```python
min_(self)
max_(self)
sum_(self)
all_(self)
any_(self)
```


Returns `min(self)`, `max(self)`, `sum(self)`, `all(self)` and `any(self)` respectively.

--- 

```python
sort_(self, key=None, reverse=False)
```

Sorts all elements from the iterable `self` in a new list and returns it. Calls
the `sorted`-function under the hood with `key` and `reverse`.

---

```python
enumerate_(self, start=0)
``` 

Returns an enumerate object from the iterable `self`.

--- 

```python
zip_(self, *iterables)
```

Iterates over `self` and all iterables in `*iterables`, producing tuples with
an item from each one.

--- 

```python
filter_(self, condition)
filterfalse_(self, condition)
```

`filter_` returns a new instance of this iterable with only the elements that
`condition` returns true.

`filterfalse_`returns a new instance of this iterable
with only the elements that `condition` returns false.

#### Reversible

```python
reverse_(self)
```

Returns a new instance of the reversed iterable `self`.

#### Sized

```python
len_(self)
```

Returns `len(self)`

#### MutableSequence

```python
map_inplace_(self, apply, *args, **kwargs)
```

Applies every element of `self` to `function` and overwrites this element with
the new value.


#### List


```python
sort_inplace_(self, key=None, reverse=False)
```

Sorts the list in-place and returns the sorted list

### Functions

```python
compose_(self, function)
```

Composes a function

---

```python
and_(self, other_function)
or_(self, other_function)
not_(self)
```

Composes two predicates.

---

```python
partial_(self, *args, **kwargs)
```

Partially applys `*args`, and `**kwargs` to `self` and returns the partial 
function.

---

```python
curry_(self, n=EMPTY)
```

Curries `self`.

---

```python
complement_(self)
```

Cunstructs a negation of `self`.

---

```python
all_fn_(self, *fs)
any_fn_(self, *fs)
none_fn_(self, *fs)
one_fn_(self, *fs)
```

Construct a predicate returning `True` when all, any, none or exactly one of 
`self` and `fs` return True. 

## Examples

Most of those functions should not need additional examples, as they are a mere
re-phrasing of some basic concepts and functions of the Python programming 
language and funcy.

If you need an example anyway, you should consider looking at the test
classes, which cover every function.


## The why
An excerpt of the "zen of python":

> Beautiful is better than ugly

A simple exercise. Initialize a list with the numbers `[4, 1, 2, 3]`, take the negative
square of these numbers, sort the list and print every element individually.

Which one of the following code pieces is more beautiful?
```python
input = [4, 1, 2, 3]
squared = [-(x ** 2) for x in input]
squared.sort()
for element in squared:
  print(x)
```

```python
def negative_square(x): return - (x ** 2)

fe.List.from_values(4, 1, 2, 3)\
       .map_inplace(negative_square)\
       .fe_sort()\
       .for_each(print)
```

There are 4 things happening. In the top example, all things are expressed via
a slightly different syntax. Initialization with a list literal, mapping by a 
list comprehension, calling the sort-function on the list and then printing all
elements 

In the bottom example, every "thing" that is happening, is expressed in a 
coherent way - by calling a function.

| Requirement                                       | Corresponding Function                       |
|---------------------------------------------------|----------------------------------------------|
| Initialize a list with the numbers `[4, 1, 2, 3]` | `.from_values(4, 1, 2, 3)`                   |
| Take the negative square                          | `.map_inplace(negative_square)` <sup>1</sup> |
| Sort the list                                     | `.fe_sort()`                                 |
| Print every element individually                  | `.for_each(print)`                           |

<sup>1</sup> we could also use a lambda-function in-place to avoid having to
declare an own function for this case. However, the code might become slightly
more expressive. Which brings us to the next "principle"

> Explicit is better than implicit

This principle means, that there shouldn't be anything unexpected happening under
the hood, which is often the case in high-level codebases. Metaprogramming,
making use of inheritance or just not choosing proper variable names can make
the code hard to read and understand, making it more easy to make mistakes.

These extensions might make things more complex at first glance, but the naming
is chosen quite carefully. If a function name corresponds to a name of a
built-in function, this functions will not do more or less than exactly that
function.

All other functions are designed to be pure, and not cause anything to happen
outside of their little scope.

All function that are not pure, are explicitly named like that, for example
`map_inplace`

All functions that have similar names than built-in functions, have an 
explicit "fe_"-prefix to denote that they do things differently, for example
`fe_sort`, which adheres to the rule that functions are pure, unlike the `sort()`-
function from the builtin `list`-class.

> Simple is better than complex

and

> Readability counts

One might argue, that any code which doesn't directly solves a certain use case
is, by definition, not simple. It might be correct to define complexity like that.
But you can also define complexity by how much time it takes you to read and 
understand code. This library tries to help reduce complexity of the code itself,
on a less abstract layer than the use-case-specific solutions are written in. By
that, hopefully those solutions also become less complex.

> There should be one-- and preferably only one --obvious way to do it.

Yes, in an attempt to fulfill other principles, this principle is arguably 
broken. Now there is one more way to write a for-loop or call a map-function.
At least I try to keep this rule fulfilled within this library. There should
be one obvious way to do one thing by using functional extensions.



