update from Peter Harris

This commit is contained in:
David Goodger 2004-02-28 19:09:16 +00:00
parent 336b800bf3
commit c0529bfcca
1 changed files with 113 additions and 84 deletions

View File

@ -1,5 +1,5 @@
PEP: 309
Title: Function Currying
Title: Partial Function Application
Version: $Revision$
Last-Modified: $Date$
Author: Peter Harris <scav@blueyonder.co.uk>
@ -8,29 +8,40 @@ Type: Standards Track
Content-Type: text/x-rst
Created: 08-Feb-2003
Python-Version: 2.4
Post-History: 10-Feb-2003, 27-Feb-2003
Post-History: 10-Feb-2003, 27-Feb-2003, 22-Feb-2004
Abstract
========
This proposal is for a curry constructor for Python that
allows a new callable to be constructed from a callable and a
partial argument list (including positional and keyword arguments).
Note: after feedback on comp.lang.python, I am persuaded that the most
accurate term for this is a 'curry', so the terminology has been
amended since the first version of this PEP.
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).
I propose a standard library module called "functional", to hold useful
higher-order functions, including the curry() class.
higher-order functions, including the implementation of partial().
Motivation
==========
Curried functions are useful as functional 'sections' or as convenient
anonymous functions for use as callbacks.
In functional programming, function currying is a way of implementing
multi-argument functions in terms of single-argument functions. A
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
programming it turns out to be very useful. Expressing a function in
terms of partial application of arguments to another function can be
both elegant and powerful, and in functional languages it is heavily
used.
In some functional languages, (e.g. Miranda) you can use an expression
such as ``(+1)`` to mean the equivalent of Python's
@ -40,22 +51,24 @@ 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.
Python has more flexible argument-passing, and so function currying cannot
be implicit in the same way. Instead, a Python programmer
will probably either define another named function or use a lambda.
But lambda syntax is not to everyone's taste, to say the least.
Python does not implement multi-argument functions by currying, so if
you want a function with partially-applied arguments you would probably
use a lambda as above, or define a named function for each instance.
We need something better.
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.
Rationale
=========
Here is one way to do a create a curried callable in Python. The
implementation below is based on improvements provided by Scott David
Daniels::
Here is one way to do a create a callable with partially-applied
arguments in Python. The implementation below is based on improvements
provided by Scott David Daniels::
class curry(object):
class partial(object):
def __init__(*args, **kw):
self = args[0]
@ -69,31 +82,52 @@ Daniels::
d = kw or self.kw
return self.fn(*(self.args + args), **d)
Note that when the curried function is called, positional arguments are
appended to those provided to the constructor, and keyword arguments
override and augment those provided to the constructor.
(A recipe similar to this has been in the Python Cookbook for some
time [1]_.)
So ``curry(operator.add, 1)`` is a bit like ``(lambda x: 1 + x)``, and
``curry(Tkinter.Label, fg='blue')`` is a callable like the Tkinter
Label class, but with a blue foreground by default.
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.
I think a built-in class called ``curry`` that behaves the same way
would be very useful.
Update: a recipe almost exactly like this has been in the Python
Cookbook for quite some time, at
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52549.
Examples of Use
===============
Update: It seems likely that a standard library implementation would
be in Python, and would have to prove its worth there before making
it into the built-ins.
So ``partial(operator.add, 1)`` is a bit like ``(lambda x: 1 + x)``.
Not an example where you see the benefits, of course.
Note too, that you could wrap a class in the same way, since
classes themselves are callable factories for objects. So in some cases,
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.
Here's a simple example that uses partial application to construct
callbacks for Tkinter widgets on the fly::
from Tkinter import Tk, Canvas, Button
import sys
from functional import partial
win = Tk()
c = Canvas(win,width=200,height=50)
c.pack()
for colour in sys.argv[1:]:
b = Button(win, text=colour, command=partial(c.config,bg=colour))
b.pack(side='left')
win.mainloop()
Abandoned Syntax Proposal
=========================
I originally suggested the syntax ``fn@(*args, **kw)``, meaning the same
as ``curry(fn, *args, **kw)``.
I originally suggested the syntax ``fn@(*args, **kw)``, meaning the
same as ``partial(fn, *args, **kw)``.
At least there are no backwards-compatibility issues because the @
character isn't a legal operator in any previous versions of Python.
@ -103,39 +137,18 @@ indirection, and the use here is also a kind of indirection.
``f@(x)`` is not ``f(x)`` , but a thing that becomes ``f(x)`` when you
call it.
(The only other connection I can see with curry is that @ looks a bit
like a section through a mushroom pakora.)
Examples of Use
---------------
Using closures as callbacks with bound arguments::
def handler(arg1, arg2, opt=0):
#whatever...
button1 = Button(window, text="Action A",
command=handler@('A', '1'))
button2 = Button(window, text="Action B",
command=handler@('B', '2', opt=1))
Convenience functions ::
nextarg = sys.argv.pop@(0)
It has not been well-received, so I withdraw this part of the
It has not been well-received, so I have withdrawn this part of the
proposal.
Feedback from comp.lang.python
==============================
Feedback from comp.lang.python and python-dev
=============================================
Among the opinions voiced were the following (which I summarise):
* Lambda is good enough.
* The @ syntax is ugly (so far, unanimous).
* 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
@ -144,13 +157,16 @@ Among the opinions voiced were the following (which I summarise):
* A curry class would indeed be a useful addition to the standard
library.
* It isn't function currying, but partial application. Hence the
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).
* For completeness, another curry class that appends curried arguments
* For completeness, another object that appends partial arguments
after those supplied in the function call (maybe called
``rightcurry``) has been suggested.
@ -160,12 +176,19 @@ 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
curried 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.
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 curry rather than closure, so I have
amended this PEP accordingly.
I concur with calling the class partial rather than curry or closure,
so I have amended the proposal in this PEP accordingly. But not
throughout: some incorrect references to 'curry' have been left in
since that's where the discussion was at the time.
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::
@ -176,11 +199,10 @@ Carl Banks posted an implementation as a real functional closure::
return fn(*(cargs + fargs), **d)
return call_fn
which he assures me is more efficient. All you lose with this
implementation is introspection and sub-classing. These are only
marginal benefits and not worth a performance hit, so this would also
do as a reference implementation of a built-in curry function rather
than a built-in curry class.
which he assures me is more efficient. You lose introspection and
sub-classing that way, but these are maybe only marginal benefits and
not worth a performance hit, so this would also do as a reference
implementation.
I also coded the class in Pyrex::
@ -201,24 +223,31 @@ I also coded the class in Pyrex::
d=kw
return self.fn(*(self.args + args), **d)
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.
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
=======
I prefer that some means to curry functions should be a built-in, with the
semantics as described, whether as a function or a callable class. However,
it should do its apprenticeship in the standard library first.
I prefer that some means to partially-apply functions and other
callables should be present in the standard library.
The standard library module ``functional`` should contain ``curry`` and
``rightcurry`` classes, and any other higher-order functions the community
want. Other functions that might belong there fall outside this PEP though.
A standard library module ``functional`` should contain an
implementation of ``partial``, and any other higher-order functions
the community want. Other functions that might belong there fall
outside the scope of this PEP though.
The @ syntax proposal is withdrawn.
The @ syntax proposal has been withrawn.
References
==========
.. [1] http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52549
Copyright