Despite the fact that Python is not pure-functional programming language, it's multi-paradigm PL and it gives you enough freedom to take credits from functional programming approach. There are theoretical and practical advantages to the functional style:
- Formal provability
- Modularity
- Composability
- Ease of debugging and testing
Fn.py
library provides you with missing "batteries" to get maximum
from functional approach even in mostly-imperative program.
More about functional approach from my Pycon UA 2012 talks: Functional Programming with Python.
from fn import _
from fn.iters import zipwith
from itertools import repeat
assert list(map(_ * 2, range(5))) == [0,2,4,6,8]
assert list(filter(_ < 10, [9,10,11])) == [9]
assert list(zipwith(_ + _)([0,1,2], repeat(10))) == [10,11,12]
More examples of using _
you can find in test
cases
declaration (attributes resolving, method calling, slicing).
Lazy-evaluated scala-style streams. Basic idea: evaluate each new
element "on demand" and share calculated elements between all created
iterators. Stream
object supports <<
operator that means pushing
new elements when it's necessary.
Simplest cases:
from fn import Stream
s = Stream() << [1,2,3,4,5]
assert list(s) == [1,2,3,4,5]
assert s[1] == 2
assert s[0:2] == [1,2]
s = Stream() << range(6) << [6,7]
assert list(s) == [0,1,2,3,4,5,6,7]
def gen():
yield 1
yield 2
yield 3
s = Stream() << gen << (4,5)
assert list(s) == [1,2,3,4,5]
Lazy-evaluated stream is useful for infinite sequences, i.e. fibonacci sequence can be calculated as:
from fn import Stream
from fn.iters import take, drop, map
from operator import add
f = Stream()
fib = f << [0, 1] << map(add, f, drop(1, f))
assert list(take(10, fib)) == [0,1,1,2,3,5,8,13,21,34]
assert fib[20] == 6765
assert fib[30:35] == [832040,1346269,2178309,3524578,5702887]
fn.F
is a useful function wrapper to provide easy-to-use partial
application and functions composition.
from fn import F, _
from operator import add, mul
# F(f, *args) means partial application
# same as functools.partial but returns fn.F instance
assert F(add, 1)(10) == 11
# F << F means functions composition,
# so (F(f) << g)(x) == f(g(x))
f = F(add, 1) << F(mul, 100)
assert list(map(f, [0, 1, 2])) == [1, 101, 201]
assert list(map(F() << str << (_ ** 2) << (_ + 1), range(3))) == ["1", "4", "9"]
You can find more examples for compositions usage in fn._
implementation source
code.
fn.op.apply
executes given function with given positional arguments
in list (or any other iterable). fn.op.flip
returns you function
that will reverse arguments order before apply.
from fn.op import apply, flip
from operator import add, sub
assert apply(add, [1, 2]) == 3
assert flip(sub)(20,10) == -10
assert list(map(apply, [add, mul], [(1,2), (10,20)])) == [3, 200]
fn.iters
module consists from two parts. First one is "unification"
of lazy functionality for few functions to work the same way in Python
2+/3+:
map
(returnsitertools.imap
in Python 2+)filter
(returnsitertools.ifilter
in Python 2+)reduce
(returnsfunctools.reduce
in Python 3+)zip
(returnsitertools.izip
in Python 2+)range
(returnsxrange
in Python 2+)filterfalse
(returnsitertools.ifilterfalse
in Python 2+)zip_longest
(returnsitertools.izip_longest
in Python 2+)
Second part of module is high-level recipes to work with iterators. Most
of them taken from Python
docs
and adopted to work both with Python 2+/3+. Such recipes as drop
,
takelast
, droplast
, splitat
, splitby
I have already
submitted as docs patch which is
review status just now.
take
,drop
takelast
,droplast
head
,tail
consume
nth
padnone
,ncycles
repeatfunc
grouper
,powerset
,pairwise
roundrobin
partition
,splitat
,splitby
flatten
zipwith
iter_except
More information about use cases you can find in docstrings for each function in source code and in test cases.
- Maybe
- Either
TODO: Implementation, code samples
Workaround for dealing with TCO without heavy stack utilization.
TODO: Implementation, code samples and documented theory.
To install fn.py
, simply:
$ pip install fn
Or, if you absolutely must:
$ easy_install fn
You can also build library from source
$ git clone https://github.com/kachayev/fn.py.git
$ cd fn.py
$ python setup.py install
"Roadmap":
- Error handling (
Maybe
,Either
from Haskell,Option
from Scala etc) - Trampolines decorator
- Add to
fn.iters
modulefoldl
,foldr
,findelem
,findindex
- C-accelerator for most modules
Ideas to think about:
- "Pipeline" notation for composition (back-order):
F() >> list >> partial(map, int)
- Curried function builder to simplify
lambda arg1: lambda arg2: ...
- Scala-style for-yield loop to simplify long map/filter blocks
- Check for open issues or open a fresh issue to start a discussion around a feature idea or a bug.
- Fork the repository on Github to start making your changes to the master branch (or branch off of it).
- Write a test which shows that the bug was fixed or that the feature works as expected.