python-peps/pep-0309.txt

282 lines
8.8 KiB
Plaintext
Raw Normal View History

2003-02-10 09:51:45 -05:00
PEP: 309
2004-08-27 09:29:47 -04:00
Title: Partial Function Application
2003-02-10 09:51:45 -05:00
Version: $Revision$
Last-Modified: $Date$
Author: Peter Harris <scav@blueyonder.co.uk>
2004-04-03 21:37:34 -05:00
Status: Accepted
2003-02-10 09:51:45 -05:00
Type: Standards Track
Content-Type: text/x-rst
Created: 08-Feb-2003
Python-Version: 2.4
2004-02-28 14:09:16 -05:00
Post-History: 10-Feb-2003, 27-Feb-2003, 22-Feb-2004
2003-02-10 09:51:45 -05:00
Abstract
========
2003-02-10 09:51:45 -05:00
2004-02-28 14:09:16 -05:00
This proposal is for a function or callable class that allows a new
callable to be constructed from a callable and a partial argument list
(including positional and keyword arguments).
2004-08-27 09:29:47 -04:00
I propose a standard library module called "functional", to hold
useful higher-order functions, including the implementation of
partial().
An implementation has been submitted to SourceForge [2]_.
2003-02-10 09:51:45 -05:00
Motivation
==========
2003-02-10 09:51:45 -05:00
2004-02-28 14:09:16 -05:00
In functional programming, function currying is a way of implementing
2004-08-27 09:29:47 -04:00
multi-argument functions in terms of single-argument functions. A
2004-02-28 14:09:16 -05:00
function with N arguments is really a function with 1 argument that
returns another function taking (N-1) arguments. Function application
in languages like Haskell and ML works such that a function call::
f x y z
actually means::
(((f x) y) z)
This would be only an obscure theoretical issue except that in actual
2004-08-27 09:29:47 -04:00
programming it turns out to be very useful. Expressing a function in
2004-02-28 14:09:16 -05:00
terms of partial application of arguments to another function can be
both elegant and powerful, and in functional languages it is heavily
used.
2003-02-10 09:51:45 -05:00
In some functional languages, (e.g. Miranda) you can use an expression
such as ``(+1)`` to mean the equivalent of Python's
``(lambda x: x + 1)``.
2003-02-10 09:51:45 -05:00
In general, languages like that are strongly typed, so the compiler
always knows the number of arguments expected and can do the right
thing when presented with a functor and less arguments than expected.
2004-02-28 14:09:16 -05:00
Python does not implement multi-argument functions by currying, so if
2004-08-27 09:29:47 -04:00
you want a function with partially-applied arguments you would
probably use a lambda as above, or define a named function for each
instance.
2003-02-10 09:51:45 -05:00
2004-02-28 14:09:16 -05:00
However, lambda syntax is not to everyone's taste, so say the least.
Furthermore, Python's flexible parameter passing using both positional
and keyword presents an opportunity to generalise the idea of partial
application and do things that lambda cannot.
2003-02-10 09:51:45 -05:00
2004-08-27 09:29:47 -04:00
Example Implementation
======================
2003-02-10 09:51:45 -05:00
2004-02-28 14:09:16 -05:00
Here is one way to do a create a callable with partially-applied
2004-08-27 09:29:47 -04:00
arguments in Python. The implementation below is based on improvements
2004-02-28 14:09:16 -05:00
provided by Scott David Daniels::
2003-02-10 09:51:45 -05:00
2004-02-28 14:09:16 -05:00
class partial(object):
2003-02-10 09:51:45 -05:00
2004-02-21 11:32:30 -05:00
def __init__(*args, **kw):
self = args[0]
self.fn, self.args, self.kw = (args[1], args[2:], kw)
2003-02-10 09:51:45 -05:00
def __call__(self, *args, **kw):
2004-02-21 11:32:30 -05:00
if kw and self.kw:
d = self.kw.copy()
d.update(kw)
else:
2004-02-21 11:32:30 -05:00
d = kw or self.kw
2003-02-10 09:51:45 -05:00
return self.fn(*(self.args + args), **d)
2004-02-28 14:09:16 -05:00
(A recipe similar to this has been in the Python Cookbook for some
time [1]_.)
Note that when the object is called as though it were a function,
positional arguments are appended to those provided to the
constructor, and keyword arguments override and augment those provided
to the constructor.
2004-08-27 09:29:47 -04:00
Positional arguments, keyword arguments or both can be supplied at
when creating the object and when calling it.
2003-02-10 09:51:45 -05:00
2004-02-28 14:09:16 -05:00
Examples of Use
===============
So ``partial(operator.add, 1)`` is a bit like ``(lambda x: 1 + x)``.
Not an example where you see the benefits, of course.
2004-08-27 09:29:47 -04:00
Note too, that you could wrap a class in the same way, since classes
themselves are callable factories for objects. So in some cases,
2004-02-28 14:09:16 -05:00
rather than defining a subclass, you can specialise classes by partial
application of the arguments to the constructor.
For example, ``partial(Tkinter.Label, fg='blue')`` makes Tkinter
Labels that have a blue foreground by default.
2003-02-10 09:51:45 -05:00
2004-02-28 14:09:16 -05:00
Here's a simple example that uses partial application to construct
callbacks for Tkinter widgets on the fly::
2003-02-10 09:51:45 -05:00
2004-02-28 14:09:16 -05:00
from Tkinter import Tk, Canvas, Button
import sys
from functional import partial
2004-02-28 14:09:16 -05:00
win = Tk()
c = Canvas(win,width=200,height=50)
c.pack()
2004-08-27 09:29:47 -04:00
2004-02-28 14:09:16 -05:00
for colour in sys.argv[1:]:
2004-08-27 09:29:47 -04:00
b = Button(win, text=colour,
command=partial(c.config, bg=colour))
2004-02-28 14:09:16 -05:00
b.pack(side='left')
win.mainloop()
2003-02-10 09:51:45 -05:00
Abandoned Syntax Proposal
=========================
2003-02-10 09:51:45 -05:00
2004-02-28 14:09:16 -05:00
I originally suggested the syntax ``fn@(*args, **kw)``, meaning the
same as ``partial(fn, *args, **kw)``.
2003-02-10 09:51:45 -05:00
The @ sign is used in some assembly languages to imply register
indirection, and the use here is also a kind of indirection.
2004-08-27 09:29:47 -04:00
``f@(x)`` is not ``f(x)``, but a thing that becomes ``f(x)`` when you
2003-02-10 09:51:45 -05:00
call it.
2004-08-27 09:29:47 -04:00
It was not well-received, so I have withdrawn this part of the
proposal. In any case, @ has been taken for the new decorator syntax.
2003-02-10 09:51:45 -05:00
2004-02-28 14:09:16 -05:00
Feedback from comp.lang.python and python-dev
=============================================
Among the opinions voiced were the following (which I summarise):
* Lambda is good enough.
2004-02-28 14:09:16 -05:00
* The @ syntax is ugly (unanimous).
* It's really a curry rather than a closure. There is an almost
identical implementation of a curry class on ActiveState's Python
Cookbook.
* A curry class would indeed be a useful addition to the standard
library.
2004-08-27 09:29:47 -04:00
* It isn't function currying, but partial application. Hence the
2004-02-28 14:09:16 -05:00
name is now proposed to be partial().
* It maybe isn't useful enough to be in the built-ins.
* The idea of a module called ``functional`` was well received, and
there are other things that belong there (for example function
composition).
2004-02-28 14:09:16 -05:00
* For completeness, another object that appends partial arguments
after those supplied in the function call (maybe called
``rightcurry``) has been suggested.
I agree that lambda is usually good enough, just not always. And I
want the possibility of useful introspection and subclassing.
I disagree that @ is particularly ugly, but it may be that I'm just
weird. We have dictionary, list and tuple literals neatly
differentiated by special punctuation -- a way of directly expressing
2004-02-28 14:09:16 -05:00
partially-applied function literals is not such a stretch. However,
not one single person has said they like it, so as far as I'm
concerned it's a dead parrot.
I concur with calling the class partial rather than curry or closure,
2004-08-27 09:29:47 -04:00
so I have amended the proposal in this PEP accordingly. But not
2004-02-28 14:09:16 -05:00
throughout: some incorrect references to 'curry' have been left in
since that's where the discussion was at the time.
2004-02-28 14:09:16 -05:00
Partially applying arguments from the right, or inserting arguments at
arbitrary positions creates its own problems, but pending discovery of
a good implementation and non-confusing semantics, I don't think it
should be ruled out.
Carl Banks posted an implementation as a real functional closure::
def curry(fn, *cargs, **ckwargs):
def call_fn(*fargs, **fkwargs):
d = ckwargs.copy()
d.update(fkwargs)
return fn(*(cargs + fargs), **d)
return call_fn
2004-08-27 09:29:47 -04:00
which he assures me is more efficient.
2004-08-27 09:29:47 -04:00
I also coded the class in Pyrex, to estimate how the performance
might be improved by coding it in C::
cdef class curry:
cdef object fn, args, kw
def __init__(self, fn, *args, **kw):
self.fn=fn
self.args=args
self.kw = kw
def __call__(self, *args, **kw):
if self.kw: # from Python Cookbook version
d = self.kw.copy()
d.update(kw)
else:
d=kw
return self.fn(*(self.args + args), **d)
2004-02-28 14:09:16 -05:00
The performance gain in Pyrex is less than 100% over the nested
function implementation, since to be fully general it has to operate
by Python API calls. For the same reason, a C implementation will be
unlikely to be much faster, so the case for a built-in coded in C is
not very strong.
Summary
=======
2004-02-28 14:09:16 -05:00
I prefer that some means to partially-apply functions and other
callables should be present in the standard library.
A standard library module ``functional`` should contain an
implementation of ``partial``, and any other higher-order functions
2004-08-27 09:29:47 -04:00
the community want. Other functions that might belong there fall
2004-02-28 14:09:16 -05:00
outside the scope of this PEP though.
2004-08-27 09:29:47 -04:00
Patches for the implementation, documentation and unit tests (SF
patches 931005_, 931007_, and 931010_ respectively) have been
submitted but not yet checked in.
A C implementation by Hye-Shik Chang has also been submitted, although
it is not expected to be included until after the Python
implementation has proven itself useful enough to be worth optimising.
2004-02-28 14:09:16 -05:00
References
==========
2004-02-28 14:09:16 -05:00
.. [1] http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52549
2004-08-27 09:29:47 -04:00
.. [2] Patches 931005_, 931007_, and 931010_.
.. _931005: http://www.python.org/sf/931005
.. _931007: http://www.python.org/sf/931007
.. _931010: http://www.python.org/sf/931010
2003-02-10 09:51:45 -05:00
Copyright
=========
This document has been placed in the public domain.
..
Local Variables:
mode: indented-text
indent-tabs-mode: nil
sentence-end-double-space: t
fill-column: 70
End: