python-peps/pep-0309.txt

240 lines
7.2 KiB
Plaintext
Raw Normal View History

2003-02-10 09:51:45 -05:00
PEP: 309
Title: Built-in Curry Type
2003-02-10 09:51:45 -05:00
Version: $Revision$
Last-Modified: $Date$
Author: Peter Harris <scav@blueyonder.co.uk>
Status: Draft
Type: Standards Track
Content-Type: text/x-rst
Created: 08-Feb-2003
Python-Version: 2.4
Post-History: 10-Feb-2003, 27-Feb-2003
2003-02-10 09:51:45 -05:00
Abstract
========
2003-02-10 09:51:45 -05:00
This proposal is for a standard curry type 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' rather than a 'closure', so the
terminology has been amended since the first version of this PEP.
I propose a standard library module called "functional", to hold useful
higher-order functions, including the curry() class.
2003-02-10 09:51:45 -05:00
Motivation
==========
2003-02-10 09:51:45 -05:00
Curried functions are useful as functional 'sections' or as convenient
2003-02-10 09:51:45 -05:00
anonymous functions for use as callbacks.
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.
Python has more flexible argument-passing, and so curries cannot be
implicit in the same way. Instead of using them, a Python programmer
2003-02-10 09:51:45 -05:00
will probably either define another named function or use a lambda.
But lambda syntax is horrible, especially when you want to do
something complex.
We need something better.
Rationale
=========
2003-02-10 09:51:45 -05:00
Here is one way to do a curry in Python::
2003-02-10 09:51:45 -05:00
class curry(object):
2003-02-10 09:51:45 -05:00
def __init__(self, fn, *args, **kw):
self.fn, self.args, self.kw = (fn, args, kw)
def __call__(self, *args, **kw):
if self.kw:
d = self.kw.copy()
d.update(kw)
else:
d = kw
2003-02-10 09:51:45 -05:00
return self.fn(*(self.args + args), **d)
Note that when the curry is called, positional arguments are
2003-02-10 09:51:45 -05:00
appended to those provided to the constructor, and keyword arguments
override and augment those provided to the constructor.
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
2003-02-10 09:51:45 -05:00
Label class, but with a blue foreground by default.
I think a built-in type called ``curry``, that behaves the same way
2003-02-10 09:51:45 -05:00
but maybe implemented more efficiently, 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.
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.
2003-02-10 09:51:45 -05:00
Abandoned Syntax Proposal
=========================
2003-02-10 09:51:45 -05:00
I originally suggested the syntax ``fn@(*args, **kw)``, meaning the same
as ``curry(fn, *args, **kw)``.
2003-02-10 09:51:45 -05:00
At least there are no backwards-compatibility issues because the @
character isn't a legal operator in any previous versions of Python.
The @ sign is used in some assembly languages to imply register
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.)
2003-02-10 09:51:45 -05:00
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'))
2003-02-10 09:51:45 -05:00
button2 = Button(window, text="Action B",
command=handler@('B', '2', opt=1))
2003-02-10 09:51:45 -05:00
Convenience functions ::
nextarg = sys.argv.pop@(0)
It has not been well-received, so I am not pursuing this as a serious
proposal.
2003-02-10 09:51:45 -05:00
Feedback from comp.lang.python
==============================
Among the opinions voiced were the following (which I summarise):
* Lambda is good enough.
* The @ syntax is ugly (so far, 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.
* 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
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
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.
I concur with calling the class curry rather than closure, so I have
amended this PEP accordingly.
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
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.
I also coded the class in Pyrex::
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)
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. Any C implementation will be unlikely to be much faster, so the
case for a builtin coded in C is not very strong.
Summary
=======
I prefer that curry should be a built-in, with the semantics as
described, whether as a function or a class. However, it should do its
apprenticeship in the standard library first.
The standard library module ``functional`` should contain ``curry`` and
``rightcurry`` classes, and any other higher-order functions the community
want. These other functions fall outside this PEP though.
The @ syntax proposal is withdrawn.
Since this proposal is now much less ambitious, I'd like to aim for
inclusion in Python 2.3.
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: