update from Peter Harris, with editorial tweaks

This commit is contained in:
David Goodger 2003-02-19 00:46:51 +00:00
parent ef58cb550d
commit 2c9fa04926
1 changed files with 117 additions and 23 deletions

View File

@ -1,5 +1,5 @@
PEP: 309
Title: Built-in Closure Type
Title: Built-in Curry Type
Version: $Revision$
Last-Modified: $Date$
Author: Peter Harris <scav@blueyonder.co.uk>
@ -8,34 +8,39 @@ Type: Standards Track
Content-Type: text/x-rst
Created: 08-Feb-2003
Python-Version: 2.4
Post-History:
Post-History: 10-Feb-2003
Abstract
=========
This proposal is for a built-in closure 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 closures is suggested (tentatively).
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
===========
Closures are useful as functional 'sections' or as convenient
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)``.
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 closures cannot be
implicit in the same way. Instead of using them, a Python programmer
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.
@ -46,9 +51,9 @@ We need something better.
Rationale
==========
Here is one way to do closures in Python::
Here is one way to do a curry in Python::
class closure(object):
class curry(object):
def __init__(self, fn, *args, **kw):
self.fn, self.args, self.kw = (fn, args, kw)
@ -58,27 +63,31 @@ Here is one way to do closures in Python::
d.update(kw)
return self.fn(*(self.args + args), **d)
Note that when the closure is called, positional arguments are
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 ``closure(operator.add,1)`` is a bit like ``(lambda x: 1+x)``, and
``closure(Tkinter.Label,fg='blue')`` is a callable like the Tkinter
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 ``closure``, that behaves the same way
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
closures are a sufficiently powerful abstraction to deserve it.
curries may be a sufficiently powerful abstraction to deserve it.
I propose the syntax ``fn@(*args,**kw)``, meaning the same as
``closure(fn,*args,**kw)``. I have no idea what havoc this would
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 @
@ -89,6 +98,9 @@ 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
---------------
@ -99,15 +111,98 @@ Using closures as callbacks with bound arguments::
#whatever...
button1 = Button(window, text="Action A",
command=handler@('A','1'))
command=handler@('A', '1'))
button2 = Button(window, text="Action B",
command=handler@('B','2',opt=1))
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
=========
@ -122,4 +217,3 @@ This document has been placed in the public domain.
sentence-end-double-space: t
fill-column: 70
End: