FunkPy
FunkPy is a small library which is trying to bring a few well-established concepts from functional programing to Python. The package was inspired by libraries like lodash and rambdajs. The monads section is modeled after Scala and influenced heavily by "Scala with Cats"
The name FunkPy comes from the necessity to choose a name, which did not clash with any exiting packages. It turned out there are plenty of those around, e.g. PyFunctional and many more abandoned projects. Moreover, there is something funky about functional programming.
Pytoolz is an active project which is well document and it is definitely worth having a look.
One of the main drivers to start this library was the frustrating switch to generators/iterators in Python 3.x. They can be incredibly powerful if one needs to efficiently process thousands of entries. They are less convenient when the list has just 3 items. Hence, funkpy.Collection
was born. Collection
provides an implementation for side-effect free operations on collections, whereas many built-in operations are anything but.
Functional programing is not complete without curry
and compose
methods available directly from funkpy.uitls
or funkpy.Collection
.
Last but not least are the glorious functor and monads from funkpy.Monads
, which many people are unfamiliar with.
FunkPy is not an attempt to re-define Python. It provides simple tools for more functional way of writing code and that's it. The implementation here does not make any claims on performance. Whereas, there are no obvious problems with functional approaches in Python, wrapping millions of values in a monad each, is not the right answer, either.
Installation
pip3 install funkpy
Examples
Collection
from funkpy import Collection as _
l = [1, 2, 3]
# concatenate n lists
_.concat(l, [4, 5, 6], [7, 8, 9], 10) # => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# append a list
_.push(l, [4, 5, 6]) # => [1, 2, 3, [4, 5, 6]]
# flatten a list of lists
_.concat(*[[1, 2, 3], [1, 2, 3]]) # => [1, 2, 3, 1, 2, 3]
# is equivalent to
_.flatten([[1, 2, 3], [1, 2, 3]]) # => [1, 2, 3, 1, 2, 3]
# It's crazy to curry and compose!
addOne = _.curry(_.strictMap)(lambda i: i+1)
addOne([1, 2, 3]) # => [2, 3, 4]
addTwo = _.compose(addOne, addOne)
addTwo([1, 2, 3]) # => [3, 4, 5]
Functors and Monads
A nice introduction to containers of different kinds is Professor Frisby's Mostly Adequate Guide to Functional Programming. If you are looking for a fun IT book to pick, go no further. "Professor Frisby" is hilarious.
Jokes aside, this package implements:
- generic
Functor
class with amap
interface; - generic
Monad
class extendingFunctor
withflatMap
; -
Option
andEither
monads.
Functors
from funkpy.Monads import Functor, Monad, Option, Either
# How to construct this functor thing?
Functor(5)
# OR
Functor.of(5)
# I love mapping functors
Functor.of("Start here").map(lambda i: i+" and keep walking!")
# and some extra magic for free allows you to compare functors based
# on the values they hold
Functor.of(5) == Functor.of(5) # => True (fingers crossed)
Functor
class is a base class which be extended to do a number of things, i.e. it is a mere template for greater things.
Monads
from funkpy.Monads import Monad, Option, Either
# This looks familiar!
my_monad = Monad.of(5)
my_monad == Monad(5) # => True
# So far so good! How about something actually useful?
my_option = Option.of({"street": "Times Ave"}) \
.map(lambda i: i.get("postcode")) \
.maybe("postcode not found") # => "postcode not found
# Get it? It is an option to have a value or not!"
Contributors
In case someone wants to contribute to this project, for whatever reasons, here are few rules (for pylint) which differ from Python's PEP8 style guide:
- longer lines are ok =>
max-line-length=150
; - but empty spaces are gone =>
indent-string=' '
(double space only); - and underscore are discouraged in function names =>
function-naming-style=camelCase
; - two spaces required inside a hanging or continued line =>
indent-after-paren=2
;
The project is linked to Travis CI and every build is tested automatically.
If, heaven forbid, you got this far and found bugs, just raise tickets on GitHub!