[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.python

Python feature request : operator for function composition

Kay Schluehr

2/3/2008 5:09:00 AM

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.
10 Answers

Arnaud Delobelle

2/3/2008 9:14:00 AM

0

On Feb 3, 5:09 am, Kay Schluehr <kay.schlu...@gmx.net> wrote:
> 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?

--
Arnaud

Kay Schluehr

2/3/2008 9:44:00 AM

0

On 3 Feb., 10:13, Arnaud Delobelle <arno...@googlemail.com> wrote:
> On Feb 3, 5:09 am, Kay Schluehr <kay.schlu...@gmx.net> wrote:
>
>
>
> > 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?
>
> --
> Arnaud

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 ).

Arnaud Delobelle

2/3/2008 9:55:00 AM

0

On Feb 3, 9:43 am, Kay Schluehr <kay.schlu...@gmx.net> wrote:
> On 3 Feb., 10:13, Arnaud Delobelle <arno...@googlemail.com> wrote:
>
>
>
> > On Feb 3, 5:09 am, Kay Schluehr <kay.schlu...@gmx.net> wrote:
>
> > > 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?
>
> > --
> > Arnaud
>
> 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.

--
Arnaud

Kay Schluehr

2/3/2008 10:52:00 AM

0

On 3 Feb., 10:55, Arnaud Delobelle <arno...@googlemail.com> wrote:
> On Feb 3, 9:43 am, Kay Schluehr <kay.schlu...@gmx.net> wrote:
>
>
>
> > On 3 Feb., 10:13, Arnaud Delobelle <arno...@googlemail.com> wrote:
>
> > > On Feb 3, 5:09 am, Kay Schluehr <kay.schlu...@gmx.net> wrote:
>
> > > > 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?
>
> > > --
> > > Arnaud
>
> > 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.
>
> --
> Arnaud

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.

George Sakkis

2/3/2008 10:35:00 PM

0

On Feb 3, 12:09 am, Kay Schluehr <kay.schlu...@gmx.net> wrote:

> 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

Kay Schluehr

2/3/2008 11:38:00 PM

0

On Feb 3, 11:34 pm, George Sakkis <george.sak...@gmail.com> wrote:
> On Feb 3, 12:09 am, Kay Schluehr <kay.schlu...@gmx.net> wrote:
>
> > 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

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





Dustan

2/4/2008 3:01:00 PM

0

On Feb 2, 11:09 pm, Kay Schluehr <kay.schlu...@gmx.net> wrote:
[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:

>>> class composer(object):
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]


>>> @composer
def double(x):
return 2*x

>>> @composer
def square(x):
return x*x

>>> double_square = double & square
>>> square_double = square & double
>>> double_square(2)
8
>>> square_double(2)
16
>>> double_square(3)
18
>>> square_double(3)
36
>>> double_square(4)
32
>>> square_double(4)
64

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

Arnaud Delobelle

2/4/2008 4:12:00 PM

0

On Feb 4, 3:00 pm, Dustan <DustanGro...@gmail.com> wrote:
> On Feb 2, 11:09 pm, Kay Schluehr <kay.schlu...@gmx.net> wrote:
> [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:
>
> >>> class composer(object):
>
> 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]
>
> >>> @composer
>
> def double(x):
> return 2*x
>
> >>> @composer
>
> def square(x):
> return x*x
>
> >>> double_square = double & square
> >>> square_double = square & double
> >>> double_square(2)
> 8
> >>> square_double(2)
> 16
> >>> double_square(3)
> 18
> >>> square_double(3)
> 36
> >>> double_square(4)
> 32
> >>> square_double(4)
>
> 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'.

--
Arnaud

Dustan

2/4/2008 4:46:00 PM

0

On Feb 4, 10:11 am, Arnaud Delobelle <arno...@googlemail.com> wrote:
> 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.

Kay Schluehr

2/4/2008 5:08:00 PM

0

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.