diff --git a/pep-0309.txt b/pep-0309.txt index fdd3b6205..ce5ccca3d 100644 --- a/pep-0309.txt +++ b/pep-0309.txt @@ -1,5 +1,5 @@ PEP: 309 -Title: Built-in Closure Type +Title: Built-in Curry Type Version: $Revision$ Last-Modified: $Date$ Author: Peter Harris @@ -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: -