python-peps/pep-0309.txt

220 lines
6.6 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
Abstract
=========
This proposal is for a built-in closure or curry type for Python that
allows a new callable to be constructed from another callable and a
partial argument list (including positional and keyword arguments). A
concise syntax shorthand for curried functions is suggested
(tentatively).
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.
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):
d = self.kw.copy()
d.update(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.
Tentative Syntax Proposal
==========================
I know syntax proposals have the odds stacked against them, and
introducing new punctuation characters is frowned upon, but I think
curries may be a sufficiently powerful abstraction to deserve it.
I suggest the syntax ``fn@(*args, **kw)``, meaning the same as
``curry(fn, *args, **kw)``. I have no idea what havoc this would
wreak on the parser.
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)
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 builtins.
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.
I think it's best as a builtin type rather than in a separate standard
library module, because it's simple and general enough. It may not be
an idiom that is very common in Python programming at the moment, but
I think that's because you have to code it yourself if you want it.
If added as a built-in feature, we would soon be wondering how we
managed without it.
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)
but I'm guessing that there would be minimal performance improvement
since it compiles to a load of Python API calls.
Summary
========
I maintain that curry should be a built-in, with the semantics as
described, whether as a function or a class.
The @ syntax proposal is withdrawn.
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: