Functional composition in python

D

Dmitry Groshev

Hello all. Some time ago I wrote a little library:
http://github.com/si14/python-functional-composition/ , inspired by
modern functional languages like F#. In my opinion it is quite useful
now, but I would like to discuss it.
An example of usage:

import os
from pyfuncomp import composable, c, _

def comment_cutter(s):
t = s.find("#")
return s if t < 0 else s[0:t].strip()

@composable #one can use a decorator to make a composable function
def empty_tester(x):
return len(x) > 0 and x[0] != "#"

path_prefix = "test"

config_parser = (c(open) >> #or use a transformer function
c(str.strip).map >> #"map" acts like a function modifier
c(comment_cutter).map >>
empty_tester.filter >> #so does "filter"
c(os.path.join)[path_prefix, _].map) #f[a, _, b] is
used to make a partial.
#f[a, foo:bar,
baz:_] is also correct

print config_parser("test.txt")
print (c("[x ** %s for x in %s]")[2, _] << c(lambda x: x * 2).map)([1, 2, 3])

Any suggestions are appreciated.
 
P

Peter Otten

Dmitry said:
Hello all. Some time ago I wrote a little library:
http://github.com/si14/python-functional-composition/ , inspired by
modern functional languages like F#. In my opinion it is quite useful
now, but I would like to discuss it.
An example of usage:

import os
from pyfuncomp import composable, c, _

def comment_cutter(s):
t = s.find("#")
return s if t < 0 else s[0:t].strip()

@composable #one can use a decorator to make a composable function
def empty_tester(x):
return len(x) > 0 and x[0] != "#"

path_prefix = "test"

config_parser = (c(open) >> #or use a transformer function
c(str.strip).map >> #"map" acts like a function modifier
c(comment_cutter).map >>
empty_tester.filter >> #so does "filter"
c(os.path.join)[path_prefix, _].map) #f[a, _, b] is
used to make a partial.
#f[a, foo:bar,
baz:_] is also correct

print config_parser("test.txt")
Any suggestions are appreciated.

With some effort you could perhaps tweak your library to accept something
like

config_parser = c(open) | str.strip | comment_cutter | empty_tester |
c(os.path.join)(path_prefix, _)

This looks more like a shell pipe than a C++ print statement -- which I
think is a good thing.

More general: Yes, I know that the functional style is contagious. However,
I find that more traditional Python code is easier to understand. Compare:

import os

def config_parser(configfile, folder):
with open(configfile) as lines:
for line in lines:
name = line.partition("#")[0].strip()
if name:
yield os.path.join(folder, name)

for path in config_parser("test.txt", "test"):
print path

(at least that's what I'm guessing your code is trying to achieve)

Peter
 
S

Steven D'Aprano

Hello all. Some time ago I wrote a little library:
http://github.com/si14/python-functional-composition/ , inspired by
modern functional languages like F#. In my opinion it is quite useful
now, but I would like to discuss it.
An example of usage:

import os
from pyfuncomp import composable, c, _

def comment_cutter(s):
t = s.find("#")
return s if t < 0 else s[0:t].strip()

@composable #one can use a decorator to make a composable function
def empty_tester(x):
return len(x) > 0 and x[0] != "#"


Why do you need a decorator to make a composable function? Surely all
functions are composable -- it is the nature of functions that you can
call one function with the output of another function.

path_prefix = "test"

config_parser = (c(open) >> #or use a transformer function
c(str.strip).map >> #"map" acts like a function modifier
c(comment_cutter).map >>
empty_tester.filter >> #so does "filter"
c(os.path.join)[path_prefix, _].map) #f[a, _, b] is
used to make a partial.
#f[a, foo:bar,
baz:_] is also correct

print config_parser("test.txt")
print (c("[x ** %s for x in %s]")[2, _] << c(lambda x: x * 2).map)([1,
2, 3])

Any suggestions are appreciated.


Did you expect us to guess what the above code would do? Without showing
the output, the above is just line noise.

What does c() do? What does compose() do that ordinary function
composition doesn't do? You say that "map" acts as a function modifier,
but don't tell us *what* it modifies or in what way. Same for filter.

So anyone not familiar with C syntax, the use of << is just line noise.
You need to at say what you're using it for.
 
D

Dmitry Groshev

Hello all. Some time ago I wrote a little library:
http://github.com/si14/python-functional-composition/, inspired by
modern functional languages like F#. In my opinion it is quite useful
now, but I would like to discuss it.
An example of usage:
import os
from pyfuncomp import composable, c, _
def comment_cutter(s):
    t = s.find("#")
    return s if t < 0 else s[0:t].strip()
@composable #one can use a decorator to make a composable function
def empty_tester(x):
    return len(x) > 0 and x[0] != "#"

Why do you need a decorator to make a composable function? Surely all
functions are composable -- it is the nature of functions that you can
call one function with the output of another function.


path_prefix = "test"
config_parser = (c(open) >>  #or use a transformer function
             c(str.strip).map >> #"map" acts like a function modifier
             c(comment_cutter).map >>
             empty_tester.filter >> #so does "filter"
             c(os.path.join)[path_prefix, _].map) #f[a, _, b] is
used to make a partial.
                                                    #f[a, foo:bar,
baz:_] is also correct
print config_parser("test.txt")
print (c("[x ** %s for x in %s]")[2, _] << c(lambda x: x * 2).map)([1,
2, 3])
Any suggestions are appreciated.

Did you expect us to guess what the above code would do? Without showing
the output, the above is just line noise.

What does c() do? What does compose() do that ordinary function
composition doesn't do? You say that "map" acts as a function modifier,
but don't tell us *what* it modifies or in what way. Same for filter.

So anyone not familiar with C syntax, the use of << is just line noise.
You need to at say what you're using it for.

Yep, it's my mistake. I thought this syntax is quite intuitive. Here
is some explanations in code:

@composable
def f1(x):
return x * 2

@composable
def f2(x):
return x + 3

@composable
def f3(x):
return (-1) * x

@composable
def f4(a):
return a + [0]

@composable
def sqrsum(x, y):
return x ** 2 + y ** 2

print f1(2) #4
print f2(2) #5
print (f1 << f2 << f1)(2) #14
print (f3 >> f2)(2) #1
print (f2 >> f3)(2) #-5
print (c(float) << f1 << f2)(4) #14.0
print (sqrsum[_, 1] << f1)(2) #17
print (sqrsum[_, _].map)([1, 2, 3, 4, 5]) #[2, 8, 18, 32, 50]
print (c(lambda x: x * 2).map >> c("[x * %s for x in %s]")[3, _])([1,
2, 3]) #[6, 12, 18]

Generally, f1 >> f2 means "lambda x: f2(f1(x))" or "pass the result of
f1 to f2". But in python function can return only one value, so a
composable function should be a function of one argument. So some form
of making partial is needed, and here comes a
f[a, b, _] notation, which means "substitute 3rd argument of f, first
twos are a and b". Finally, we need some form of syntactic sugar for
this: c(map)[c(f),_], so we have a "map" modifier, which transforms
function F to an isomorphism or mapping between lists. For example,
c(lambda x: x * 2).map is equal to lambda x: map(lambda y: y * 2, x).
"Filter" modifier is the same thing for boolean functions.
What does c() do? What does compose() do that ordinary function
composition doesn't do?
I need c() or composable() to make an objects with overloaded
operators.

All in all, all this stuff is just a syntactic sugar for nested
functions, maps and filters, which brings a new semantics for old
operators (so one can call it edsl).
 
A

Audric Schiltknecht

Le 29/08/2010 04:54, Dmitry Groshev a écrit :
[snip]

Generally, f1>> f2 means "lambda x: f2(f1(x))" or "pass the result of
f1 to f2". But in python function can return only one value, so a
composable function should be a function of one argument.

You can use a tuple to return more than just one value:

def f(a,b):
return (a,b)
(1,2)


Ok, technically, it is ONE value, but since you can access any member of
this tuple :)
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,582
Members
45,070
Latest member
BiogenixGummies

Latest Threads

Top