ActiveState Code

Recipe 52549: curry -- associating parameters with a function


In functional programming, currying is a way to bind arguments with a function and wait for the rest of the arguments to show up later. You "curry in" the first few parameters to a function, giving you a function that takes subsequent parameters as input and calls the original with all of those parameters. This recipe uses a class instance to hold the parameters before their first use. For example:

double = curry(operator.mul, 2)
triple = curry(operator.mul, 3)
Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class curry:
    def __init__(self, fun, *args, **kwargs):
        self.fun = fun
        self.pending = args[:]
        self.kwargs = kwargs.copy()

    def __call__(self, *args, **kwargs):
        if kwargs and self.kwargs:
            kw = self.kwargs.copy()
            kw.update(kwargs)
        else:
            kw = kwargs or self.kwargs

        return self.fun(*(self.pending + args), **kw)

Discussion

A typical use of curry is to construct callback functions for GUI operations. When the operation does not really merit a new function name, curry can be useful in creating these little functions. This can be the case with commands for buttons, for example.

self.button = Button(frame, text='A', command=curry(transcript.append, 'A'))

Curry can also be used interactively by making versions of your functions with debugging-appropriate defaults or initial parameters filled in for your current case. For example, database debugging work might well begin by setting:

Connect = curry(ODBC.Connect, dsn='MyDataSet')

If you are creating a function for regular use, and there is a good choice for a name, the 'def fun(...' form of function definition is usually more readable, and often more easily extended. As you can see from the implementation, no magic happens to "specialize" the function with the provided parameters; curry should be used when you feel the code is more clear with its use than without. Typically this will be to emphasize that you are only providing parameters to a "commonly used" (in this application) function, not providing separate processing.

Comments

  1. 1. At 10:19 a.m. on 24 may 2001, Nick Perkins said:

    'Lightweight' subclasses. This also works very well for creating a sort of lightwieght subclass..

    ie. you can curry the constructor of a class to give the illusion of a subclass as follows:

    bluewindow = curry(window, bg='blue')

    bw = bluewindow()

    ..of course type(bluewindow) is still type(window), ( not a sub-type )

    Additional parameters can still be passed to the curried constructor:

    bw2 = bluewindow( title='blah', fg='yellow')

    curry is cool, and not just for callbacks!

  2. 2. At 2:54 p.m. on 20 jun 2001, Alex Martelli said:

    curry-by-closure. Lexically nested scopes (in Python 2.2 -- "from __future__ import" if you want to use them in 2.1) allow interesting currying too, e.g.:

    def curry(func, *args, **kwds):
        def callit(*moreargs, **morekwds):
            kw = kwds.copy()
            kw.update(morekwds)
            return func(*(moreargs+args), **kw)
        return callit
    

    This curries positional arguments from the right, and gives named arguments specified at call-time precedence over those specified at currying-time, but these policies are clearly easy to alter:-).

    _Without_ nested scopes, I don't think it can be done with this level of generality (injecting names into the inner scope becomes hard to do generally when one wants fully-general args and *kwds...:-).

    Alex

  3. 3. At 10:29 p.m. on 23 jun 2001, Scott David Daniels (the author) said:

    Using curry to wrap debugging information around calls. The 'curry' function can be used in debugging as well. For example, we can "wrap" method calls in objects:

    def report(originalFunction, name, *args, **kw):
      print name + '(', ', '.join(map(repr,args) +
                        [k+'='+repr(kw[k]) for k in kw.keys()]), ')'
      result = originalFunction(*args, **kw)
      if result: print name, '==>', result
      return result
    
    class Sink:
      def write(self, text):
        pass
    
    dest = Sink()
    dest.write = curry(report, dest.write, 'write')
    print >>dest, 'this', 'is', 1, 'test'
    
  4. 4. At 5:09 a.m. on 28 jun 2001, Alex Martelli said:

    alternative curry-by-closure. Nick Perkins' latest version of curry-by-closure (from c.l.p, for the record) is more general (no named args at all, thus no accidental name capture -- except, I think, for args and create_time_kwds...) and quite readable, although marginally more verbose, due to good name choices for intermediate locals. It also has curry-from-left (for positional args) and call-time-dominates (for named args) semantics, which may be popular:

    def curry(*args, **create_time_kwds):
        func = args[0]
        create_time_args = args[1:]
        def curried_function(*call_time_args, **call_time_kwds):
            args = create_time_args + call_time_args
            kwds = create_time_kwds.copy()
            kwds.update(call_time_kwds)
            return func(*args, **kwds)
        return curried_function
    
  5. 5. At 10:11 a.m. on 13 nov 2002, Tracy Ruggles said:

    Enhancement: the original function's __doc__ string... Why not also add this before returning the curried function:

    curried_function.__doc__ = "Curried function '%s':\n%s" % (
        func.func_name, func.func_doc
        )
    
  6. 6. At 5:40 a.m. on 19 feb 2004, David Abrahams said:

    This isn't currying. The operation performed by the operator described here is actually called "Partial Application". A good definition of currying can be inferred from http://tinyurl.com/3d6w6 and http://tinyurl.com/ly29.

    Several languages have misused the term "curry" this way

     http://tinyurl.com/2p6gb, http://tinyurl.com/2onya (Python)
    
     http://tinyurl.com/36pus (JavaScript)
    
     http://tinyurl.com/3dj6j (Dylan)
    

    Currying transforms a function taking a single tuple argument into a function taking multiple arguments, and uncurrying reverses the process.

    >>> def curry(f):
    ...     return lambda *args: f(args)
    ...
    >>> def add2(args): # add the first 2 elements of an argument tuple
    ...     return args[0] + args[1]
    ...
    >>> curry(add2)(1,2) # make add2 accept multiple args
    3
    >>> def uncurry(f):
    ...     return lambda x: f(*x)
    ...
    >>> uncurry(curry(add2))((1,2)) # reverse currying; pass a tuple
    3
    
  7. 7. At 8:45 a.m. on 23 jan 2005, Scott David Daniels (the author) said:

    Current curry code. I'd eliminate some micro-optimizations today and avoid spurious copies. Also, to allow named parameters such as 'self' to be specified in keywords, _no_ directly named args are provided.

    class curry:
        def __init__(*args, **kwargs):
            self = args[0]
            self.fun = args[1]
            self.pending = args[2:]
            self.kwargs = kwargs
    
        def __call__(*args, **kwargs):
            self = args[0]
            kw = self.kwargs.copy()
            kw.update(kwargs)
            return self.fun(*self.pending + args, **kw)
    

    The above works back through _many_ versions of python (though you might have to use "apply" to call self.fun if your Python is old). "Nested scopes" became a feature after this code was written. If I were to write it now, I might write:

    def curry(*args, **kwargs):
        function, args = args[0], args[1:]
        def result(*rest, **kwrest):
            combined = kwargs.copy()
            combined.update(kwrest)
            return function(*args + rest, **combined)
        return result
    

    Finally, putting the micro-optimizations (and one new one) back in:

    import new
    
    def curry(*args, **kwargs):
        function, args = args[0], args[1:]
        if args and kwargs:
            def result(*rest, **kwrest):
                combined = kwargs.copy()
                combined.update(kwrest)
                return function(*args + rest, **combined)
        elif args:
            if len(args) > 1:
                def result(*rest, **kwrest):
                    return function(*args + rest, **kwrest)
            else:
                # Special magic: make a bound object method on the arg
                return new.instancemethod(function, args[0], object)
        elif kwargs:
            def result(*rest, **kwrest):
                if kwrest:
                    combined = kwargs.copy()
                    combined.update(kwrest)
                else:
                    combined = kwargs
                return function(*rest, **combined)
        else:
            return function
        return result
    
  8. 8. At 2:13 p.m. on 22 jan 2006, Scott David Daniels (the author) said:

    Warning: special case. Change needed above. In the code above, bonono recently pointed out that instancemethod will not do what I want with a value of None, so the code above should be changed from:

    ...
    elif args:
        if len(args) > 1:
    ...
    

    to:

    ...
    elif args:
        if len(args) > 1 or args[0] is None:
    ...
    
  9. 9. At 12:53 p.m. on 2 mar 2006, Peter Bratlovksy said:

    can i use this code in commercial project? Hi. I wanna include code in commercial project, do you mind? :)

    http://mortgage-calculator.teach-nology.com

  10. 10. At 8:36 a.m. on 27 mar 2006, Marko Mikulicic said:

    Curryng methods. I've found that using the closure you cannot apply curry to instance methods and classmethods using:

    class X(object):
       def test(self, arg):
          ..
       curried = curry(test, 10)
    ...
    

    However with the addition of the decorator protocol to the class based implementation

    class curry(object):
        ....
        ....
        def __get__(self, obj, typ):
            """
            This method is an addition to the python cook book receipe.
    
            It allows partial function to be applied to method and classmethods
            """
            return curry(self.fn.__get__(obj, typ), *self.args, **self.kw)
    

    perhaps it could be done better but it works (even with classmethod)

Sign in to comment