An open API service indexing awesome lists of open source software.

https://github.com/opeltre/fp

Functional programming in python
https://github.com/opeltre/fp

fp functors monad-transformers monads types

Last synced: 19 days ago
JSON representation

Functional programming in python

Awesome Lists containing this project

README

        

# fp

A lightweight functional programming library with
[numpy], [jax] and [torch] support.

- 🗒️ [docs]
- 📁 [repository]

[docs]:https://funprogram.readthedocs.io/en/latest/
[repository]:https://github.com/opeltre/fp
[numpy]:https://numpy.org
[jax]:https://jax.readthedocs.io/en/latest/
[torch]:https://pytorch.org/docs

## Installation

This library is still under development.
You can install the latest 0.9.2 beta release from PyPI or clone
the project and build `fp` with poetry.

### Install from PyPI

```
pip install funprogram
```

### Install with poetry

You can use [poetry](https://python-poetry.org/) to build wheels
from a specific commit. To install the poetry environment and try
using `fp`, you can do:
```bash
$ git clone https://github.com/opeltre/fp
$ cd fp && poetry lock && poetry install
$ poetry run python -i examples/barbaz.py
```

## Overview

The `fp` library relies on python [metaclasses] to emulate a static python type system of `Type` instances.

Its motivation is to provide a strict yet flexible interface
to a polymorphic type system, implementing most the
abstractions of a [cartesian closed] category.
Being familiar with category theory is not a prerequisite for using `fp`.
The package and documentation are also intended as a user-friendy
and didactic tools for getting used with functional programming concepts.

[cartesian closed]: https://en.wikipedia.org/wiki/Cartesian_closed_category

[metaclasses]: https://www.python.org/dev/peps/pep-3115/

Type polymorphism was a necessary feature for the
downstream message-passing library [topos] that I started developing
during my PhD.
Overcoming the mysterious difficulties of metaclass construction was a hard enough task for this latter project to ever really reach a definitive state, but development might perhaps resume as stable versions of the `fp` package are released.

[topos]: https://github.com/opeltre/topos

### `fp.cartesian` module

The type `Hom(A, B)` declares typed functions or type _morphisms_, with input in `A` and output in `B`. Functions with multiple inputs can be declared by supplying a tuple of types `A = (A0, A1, ...)` as input.

```py
from fp import *

@Hom((Int, Str), Str)
def foo(n, s):
return ("-" * n).join([s] * n)
```

In `fp`, typed functions are instances of the "top" type `Hom.Object` which takes care automatic currying, i.e. returning the partial application of
the decorated callable when invoked with too few arguments.

```py
>>> foo
Int -> Str -> Str: foo
>>> foo(2)
Str -> Str: foo 2
>>> foo(2)("Hello World!")
Str: "Hello World!--Hello World! "
```

Typed functions (including curried ones) can be composed with the `@` operator:

```py
>>> bar = Hom(Int, Str)(lambda n: '|' * n)
>>> baz = Int.mul
>>> foo(4) @ bar @ baz
Int -> Str: foo 4 . λ . mul 3
>>> (foo(4) @ bar @ baz(3))(2)
Str: "||||||----||||||----||||||----||||||"
```

### `fp.instances` module

Algebraic subclasses of `Type` are defined in `fp.instances.algebra`,
allowing transparent subclassing of numeric builtin types. The lifting and propagation of algebraic methods defined there is also used by `Str`
and `List.Object`, by being declared instances of the `Monoid` type class.

```py
>>> from fp import Int, Float, Str, List
>>> greet = Str.add("👋 Welcome")
>>> greet
Str -> Str: add 👋 Welcome
>>> greet("! " + foo(2)(bar(3)))
"👋 Welcome! |||--|||"
```

The `List` functor creates parameterized types inheriting from `List.Object`,
a subclass of the builtin `list`, enriched with a `map` method.
The `map` method of a functorial type `List(A)`
is an alias for the `List.fmap` class method, of signature:

```py
>>> List.fmap
(A -> B) -> List A -> List B
```
The target type `B` only needs to be explicited when `List(A).map` is called with
an untyped function argument.

```py
>>> phone = List(Int)("0632501202")
>>> phone.map(lambda n: 2 << n, tgt=Int)
List Int : [1, 64, 8, 4, 32, 1, 2, 4, 1, 4]
>>> List.fmap(bar)
List Int -> List Str: map λ
>>> List.fmap(bar)(phone)
List Str : ['', '||||||', '|||', '||', '|||||', '', '|', '||', '', '||']
```

Other features include a `State` monad with which one might indeed write
fun programs 💻🐒!

```py
# `State(Int, Str)` subclasses `Int -> (Int, Str)`

@State(Int, Str)
def barbaz(n):
"""Update an integer and return a string."""
q, r = divmod(n, 2)
if q == 0:
return q, "|" if r else "*"
return q, "|:" if r else "*:"

# The `State(Int)` monad binds `a -> State(Int, b)` functions

@Hom(Str, State(Int, Str))
def foobarbaz(acc):
"""Do `barbaz` while the accumulator says to continue."""
# run with io.VERBOSITY = 1 to see intermediate steps
io.log(acc, v=1)
if not len(acc) or acc[-1] != ":":
# return accumulator
else:
# accumulate barbaz recursively
accbarbaz = barbaz.map(Str.add(acc[:-1]))
# bind foobarbaz
return accbarbaz >> foobarbaz
else:
# return accumulator
return State(Int).unit(acc)

>>> foobarbaz(":").run(257)
(Int, Str): (0, '|*******|')
>>> foobarbaz(":")(260)
Str: '**|*****|'
```
Other monads have found plenty of applications
in abstracting IO contexts, error handling, data streams and asynchronous
threads.

See the [docs] or the [source][instances] for an exhaustive list of
the currently implemented types, functors, monads, etc.

[instances]: https://github.com/opeltre/fp/blob/master/fp/instances/__init__.py

### `fp.tensors` module

For now, the `Tensor` class is an alias of `Torch`, while Arrays from other backends can be explictly created with `Jax` and `Numpy`. This part of the API is
subject to change.

```py
>>> from fp.tensors import Torch, Jax, Numpy
>>> from fp.tensors import backends
>>> backends
List Backend: [Numpy, Jax, Torch]
>>> x, y, z = (B.range(3) * (10 ** i) for i, B in enumerate(backends))
>>> x
Numpy: [0 1 2]
>>> y + x.jax()
Jax: [0 11 22]
>>> z + (x + y.numpy()).torch()
Torch: [0, 111, 222]
```

Typed tensors are created by supplying shape tuples to the `Tens` functor.
With `Linear` and `Otimes`, typed tensors form a [closed monoidal]
subcategory of `Type`.

[closed monoidal]: https://en.wikipedia.org/wiki/Closed_monoidal_category

```py
>>> from fp.tensors import Tens, Linear
>>> E = Tens((2, 3))
>>> F = Tens((4,))
>>> v = E.range()
Tens 2x3 : [[0, 1, 2],
[[3, 4, 5]]
>>> f = Linear(E, F)(Tensor.randn(4, 6))
>>> f(v).shape
(4,)
```

See [examples/arrays.py](examples.arrays.py) and the [docs] for more details.

## Contributing and troubleshooting

If you use `fp` and experience bugs or inconsistencies,
please report an issue on the
[github](htts://github.com/opeltre/fp/issues) page.
When debugging `fp` code, setting the following should be useful:

```py
fp.io.VERBOSITY = 3
```