Python feature request : operator for function composition

K

Kay Schluehr

As you know, there is no operator for function composition in Python.
When you have two functions F and G and want to express the
composition F o G you have to create a new closure

lambda *args, **kwd: F (G (*args, **kwd))

or you write a composition in functional style

compose( F, G )

None of these solutions is particular terse.

Proposal
-------------
I want to propose overloading the logical __and__ operator i.e. '&'
for functions s.t. F & G means "compose(F,G)". This suggestion is non-
conflicting since & is not used as an operator in combination with
function objects yet: given a function object F and an arbitrary
object X the combination F & X raises a TypeError when evaluated.

Alternatives
 
A

Arnaud Delobelle

As you know, there is no operator for function composition in Python.
When you have two functions F and G and  want to express the
composition F o G you have to create a new closure

lambda *args, **kwd: F (G (*args, **kwd))

or you write a composition in functional style

compose( F, G )

None of these solutions is particular terse.

Proposal
-------------
I want to propose overloading the logical __and__ operator i.e. '&'
for functions s.t. F & G means "compose(F,G)". This suggestion is non-
conflicting since & is not used as an operator in combination with
function objects yet: given a function object F and an arbitrary
object X the combination F & X raises a TypeError when evaluated.

Alternatives
-----------------
One could use other unused operators like F << G or F * G to write
terse function composition expressions. I' m not sure which one is
best and would use the proposed & if no one presents arguments against
it.

What about other callable objects?
 
K

Kay Schluehr

What about other callable objects?

Supporting general callables would be very fine. I thing a callable
ABC with two protocols named __call__ and __compose__ would be most
adequate. I'm just not sure if Python 2.X requires a more ad hoc
implementation for builtin callable types? On the level of user
defined classes a bundling between __call__ and __compose__ shall
remain optional ( unlike e.g. the dependency between __iter__ and
__next__ for iterables ).
 
A

Arnaud Delobelle

Supporting general callables would be very fine. I thing a callable
ABC with two protocols named __call__ and __compose__ would be most
adequate. I'm just not sure if Python 2.X requires a more ad hoc
implementation for builtin callable types? On the level of user
defined classes a bundling between __call__ and __compose__ shall
remain optional ( unlike e.g. the dependency between __iter__ and
__next__ for iterables ).

This would conflict with the fact that the operators you suggest for
composition can already be overloaded for other purposes.
 
K

Kay Schluehr

This would conflict with the fact that the operators you suggest for
composition can already be overloaded for other purposes.

True. Extending a callable ABC by __add__ shall be sufficient and does
not cause ambiguities, Just one tiny teardrop for __add__ not being
very telling. Otherwise the intended semantics might be constrained to
builtin function/method/... types and classes that derive from a
callable ABCs. This is sufficient for covering the intended purpose of
function composition and does not exclude close relatives ( methods,
function-like objects ). How someone assigns semantics to a general
__call__ on his own classes is up to him.
 
G

George Sakkis

As you know, there is no operator for function composition in Python.
When you have two functions F and G and want to express the
composition F o G you have to create a new closure

lambda *args, **kwd: F (G (*args, **kwd))

or you write a composition in functional style

compose( F, G )

None of these solutions is particular terse.

What if F takes more than one (positional and/or keyword) arguments?
How common is this special use case where F takes a single argument
(the result of G) to deserve a special operator ?

George
 
K

Kay Schluehr

What if F takes more than one (positional and/or keyword) arguments?
How common is this special use case where F takes a single argument
(the result of G) to deserve a special operator ?

George

O.K. Point taken. Here is a more general form of compose that is
applicable when both F and G take *args and **kwd arguments.

def compose(F,G):
def prepare_args(args, kwd = {}):
return args if isinstance(args, tuple) else (args,), kwd
def apply_composition(*args, **kwd):
nargs, nkwd = prepare_args(G(*args, **kwd))
return F(*nargs, **nkwd)
return apply_composition
 
D

Dustan

[snip]

While you're waiting for it to be implemented, you can build your own
version as a decorator. Here's an example written in haste:
def __init__(self, *funcs):
self.funcs = funcs
def __and__(self, other):
if isinstance(other, composer):
return composer(*(self.funcs+other.funcs))
else:
return composer(*(self.funcs+(other,)))
def __call__(self, *args, **kargs):
for func in reversed(self.funcs):
args = (func(*args, **kargs),)
if kargs:
kargs = {}
return args[0]

def double(x):
return 2*x
def square(x):
return x*x
64

Probably not the best implementation, but you get the idea.
 
A

Arnaud Delobelle

[snip]

While you're waiting for it to be implemented, you can build your own
version as a decorator. Here's an example written in haste:

def __init__(self, *funcs):
self.funcs = funcs
def __and__(self, other):
if isinstance(other, composer):
return composer(*(self.funcs+other.funcs))
else:
return composer(*(self.funcs+(other,)))
def __call__(self, *args, **kargs):
for func in reversed(self.funcs):
args = (func(*args, **kargs),)
if kargs:
kargs = {}
return args[0]

def double(x):
return 2*x

def square(x):
return x*x

64

Probably not the best implementation, but you get the idea.

This is nice.
* I wouldn't choose '&' as the composing operator as when I read
'double & square' I think 'take an x, double it & square it' which is
the wrong interpretation (perhaps << instead?).
* I would call the decorator 'composable'.
 
D

Dustan

This is nice.
Thanks.

* I wouldn't choose '&' as the composing operator as when I read
'double & square' I think 'take an x, double it & square it' which is
the wrong interpretation (perhaps << instead?).

A very good point that I didn't think about; I just blindly took the
OP's chosen operator. Another thing I realized after writing this was
that I could have also written a corresponding __rand__ method
(__rlshift__ with your alternative operator), in case the object to
the right of the operator is a composer object and to the left is a
simple function.
* I would call the decorator 'composable'.

The thing about that, though, is that this can also be used as a
composition in function style. However, I can't think of any name that
encompasses both uses. And you're right in that composer wasn't a very
good choice of name. As I say, it was written in haste.
 
K

Kay Schluehr

This won't work for builtin functions. It hardly works for functions
and methods defined in 3rd party modules and in no way for functions
defined in C extensions. It adds boilerplate statically to remove it
at runtime.
 

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

No members online now.

Forum statistics

Threads
473,968
Messages
2,570,154
Members
46,702
Latest member
LukasConde

Latest Threads

Top