PEP: 309 Title: Built-in Curry Type Version: $Revision$ Last-Modified: $Date$ Author: Peter Harris 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: