opeltre / fp

Functional programming in python

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Install

git clone 'https://github.com/opeltre/fp'
cd fp && pip install -r requirements.txt && pip install -e ./

Types in Python

The python language allows some kind of functorial and polymorphic constructs via metaclasses: they are synonyms of type constructors, allowing to dynamically create types and customize type construction (providing a functionality somehow close to what C++ templates would do).

This library constructs a functional type theory inside python's runtime type system, by defining e.g. functor, monad, ... metaclasses and common class instances e.g. Float, Str, List, State, ... including a convenient Tensor API obtained by applying a wrapper monad to the torch.Tensor type.

>>> import fp
>>> from fp import List, Str, Int

#-- List type constructor 
>>> type(List)
Functor : * -> *
>>> List(Str)
Type : List Str
>>> x = List(Str)(["hello", "world", "!"])

#-- List functor
>>> f = Str.len
>>> List.fmap(f)
List Str -> List Int : fmap len
>>> List.fmap(f)(x)
List Int : [5, 5, 1]

Types are a prerequisite for functorial constructs, which have a vast diversity of applications in functional languages e.g. container data types, stateful programs, (a)synchronous I/O operations, log and error handling..

Type polymorphism (emulated by python metaclasses) is a powerful way to generate sister classes automatically . Just like C++ templates would allow, one can append functionality to existing code in a flexible and robust manner, without repeating code or creating tangled inheritance diagrams.

Typed functions

Arrow types

from fp import Arrow

@Arrow(Int, Str)
def bar(n):
    return '|' * n

foo = Arrow(Str, Int)(len)

Composition

>>> foo @ bar
Int -> Int : foo . bar
>>> (bar @ foo)('hello world!')
Str : '||||||||||||'

Automatic curryfication

>>> Int.add
Int -> Int  -> Int : add
>>> Int.add(2, 3)
Int : 5
>>> Int.add(2)
Int -> Int : add 2
>>> List.fmap(Int.add(2))([3, 6, 9])
List Int : [5, 7, 11]

Typed tensors

The (type unsafe) torch.Tensor class is wrapped inside the fp.Tensor class. This is taken care of by a custom Wrap monad lifting algebraic methods e.g. +, -, * to the wrapping class.

from fp import Tensor
import torch

>>> x = Tensor.randn([3])
>>> x.data.dtype, x.data.device
(torch.float32, device(type='cpu'))

# No error raised
>>> y = Tensor.ones([4])
>>> Tensor.mul(x) @ Tensor.add(y)
Tensor -> Tensor : mul [1.8948, -0.6545, -0.2041] . add [1., 1., 1., 1.]

Specific tensor types are obtained by the Tens type constructor:

from fp import Tens

>>> Tx = Tens([3])
>>> Tx.ones()
Tens 3 : [1., 1., 1.]

Linear maps acting by n x m matrices subclass both Tens([n, m]) and Arrow(Tens([m]), Tens([n])):

>>> from fp import Linear

>>> f = Linear([3], [4]).randn()
>>> f
Linear 3 -> 4 : dense 4x3
>>> x = Tx.ones()
>>> f(x)
Tens 4 : [-5.5333,  2.7625,  3.0484, -1.0863]

Given an index map f : Shape(A) -> Shape(B), an algebra morphism Tens(B) -> Tens(A) by letting xf[i] = x[f(i)]. Hence Tens is a contravariant functor on domain shapes, with linear adjunction of algebra morphism defining a symmetric covariant functor structure.

>>> Txy = Tens([3, 6])
#-- Algebra embeddings 
>>> j0 = Txy.embed(0)
>>> j0 = Txy.cofmap(Txy.domain.res(0))
>>> j0 
Linear 3 -> 3x6 : sparse 18x3 (nnz=18)
#-- Partial integration maps
>>> p0 = Txy.proj(0)
>>> p0 = Txy.cofmap(Txy.domain.res(0)).t()
>>> p0
Linear 3x6 -> 3 : sparse 3x18 (nnz=18)

About

Functional programming in python

License:MIT License


Languages

Language:Python 100.0%