python-peps/pep-0309.txt

240 lines
7.2 KiB
Plaintext
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

PEP: 309
Title: Built-in Curry Type
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
Abstract
========
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.
Motivation
==========
Curried functions are useful as functional 'sections' or as convenient
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)``.
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
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
=========
Here is one way to do a curry in Python::
class curry(object):
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
return self.fn(*(self.args + args), **d)
Note that when the curry is called, positional arguments are
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
Label class, but with a blue foreground by default.
I think a built-in type called ``curry``, that behaves the same way
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.
Abandoned Syntax Proposal
=========================
I originally suggested the syntax ``fn@(*args, **kw)``, meaning the same
as ``curry(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.
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.)
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 am not pursuing this as a serious
proposal.
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.
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: