Skip to content

edwardbadboy/pyfunctional

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

7 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Usage:
./tranToken testCase.py | python


Explaination:
There is a web page on Python official site showing how to do functional programming in Python (http://docs.python.org/howto/functional.html). There you can see some discussions on generators, functools, itertools, partial apply, operator module and so on. I think it lacks function composition, and the calling the "partial" function for currying is a bit verbose, so I add two extensions to Python. The first one is a function composition class, the other is a currying operator.

1. Function Composition (函数复合)
It works like this:

------ Python Code ------ 
def f1(x):
    return x * 2

def f2(x):
    return x + 2

def f3(x):
    return x ** 2

print f3(f2(f1(3)))  # will print 64
F = fcp() * f3 * f2 * f1
print F(3)  # will print 64
------ Python Code ------ 

Here "fcp()" will instantiate a function composition class I wrote. It overrides the "*" operator to do function composition. "fcp() * f3 * f2 * ..." will result a series of function compositions. In the above example, the result function is assigned to a variable, then we can pass it to "map", "filter" or other high order functions, or just invoke it directly.


2. Currying (科里化)
Currying can make function composition more interesting. Currying is the advanced version of "partial" in "functools" module.

2.1 Two Simple Examples
Consider this one:

------ Python Code ------ 
from operator import add, mul
F = fcp() * $mul(2) * $add(1)
print F(2)
------ Python Code ------ 

Here "$" is the language extension I add to Python. "$mul(2)" means the same as "partial(mul, 2)", so "fcp() * $mul(2) * $add(1)" means "partial (mul, 2 )(partial (add, 1))".
So "F(2)" really means "2 * (1 + 2)". Sounds wired but it's really useful, because you can pass "F" everywhere as a normal function, this means you can construct some logic dynamically and pass it to another part of the program, interesting, right?

Consider how we can use function composition and currying to write a dot product function like this:

------ Python Code ------ 
from operator import add, mul

def dotProduct(addOp, mulOp, *vectors):
    f = fcp() * $reduce(addOp) * $imap(mulOp)
    return f(*vectors)

print dotProduct(add, mul, [1, 2, 3], [4, 5, 6])
------ Python Code ------ 

Firstly, "imap" in Python is like "zipwith" in other functional languages. It accepts a function "F" and two lists, fetches two element from the lists respectively, then calls the function "F" with the elements, appends the result to a list. So "$imap(mulOp)" is a function that accepts two lists and calculate the product of the corresponding elements and generates a new list.
Then we composite "$reduce(addOp)" and "$imap(mulOp)". Just expand the whole expression into normal Python expression, we can get: "reduce(addOp, imap(mulOp, *vectors))". Is it a bit clearer now?

2.2 An Advanced Example
If you think the above two examples are meaningless, here comes an advanced example.

Consider we want to find odd square numbers that less than 1000. Using currying and function composition, we can write the code like this

------ Python Code ------ 
from itertools import count as icount
from itertools import imap, ifilter, takewhile
from operator import pow, gt

def isOdd(x):
    return (x % 2) != 0

def flip(f):
    return lambda a, b: f(b, a)

R = fcp() * $takewhile($gt(1000)) * $ifilter(isOdd) * $imap($(flip(pow))(2)) * icount

for i in R(1):
    print i
------ Python Code ------ 

Here "flip" is to return a function that take the parameters reversely from the original function.
"icount(x)" gives an infinite generator counting x, x+1, ....
"imap(fun, iter)" works like map, but uses generators.
"ifilter(fun, iter)" works like filter, but uses generators.
"itakewhile(fun, iter)" returns prefix of series of items generated by iter when fun(item) is true.
So, "$takewhile($gt(1000)) " is a function which takes a sequence then yields first ones of elements while elements < 1000.
"$ifilter(isOdd)" is a function filters odd elements from a sequence.
"$(flip(pow))(2)" is a function which returns the square of the argument.
"$imap($(flip(pow))(2)) " is a function takes a sequence and squares all the elements.
Then we compose them using "fcp() *" and get a new function "R".

Calling "for i in R(1):" will take 1 element each time, the whole function chain will calculate for this result then pauses to wait for the next round. They will not compute all the results then pass to the next function in the chain. If they do, the chain can not stop, because icount is infinite. So the chain is lazy, it will just compute things as much as you need them, no more, no less. Yes, lazy evaluation is its scientific name. Using this technique, you can describe and process infinite data structures. List comprehension can get the same result and more readable, but it's not lazy, so it will not stop and burns your CPU to the end of universe. We can use generator expressions instead to get the same result, but function composition and currying can be used in a wide range area when you program.


3. Download the Source Code and Try
https://github.com/edwardbadboy/pyfunctional

About

some functional fun in Python

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages