|
|
 |
|
Title: curry -- associating parameters with a function
Submitter: Scott David Daniels
(other recipes)
Last Updated: 2001/04/18
Version no: 1.2
Category:
Programs
|
|
5 vote(s)
|
|
|
|
Description:
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)
Source: Text Source
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.
|
|
Add comment
|
|
Number of comments: 10
'Lightweight' subclasses, Nick Perkins, 2001/05/24
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!
Add comment
curry-by-closure, Alex Martelli, 2001/06/20
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
Add comment
alternative curry-by-closure, Alex Martelli, 2001/06/28
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
Add comment
Enhancement: the original function's __doc__ string..., Tracy Ruggles, 2002/11/13
Why not also add this before returning the curried function:
curried_function.__doc__ = "Curried function '%s':\n%s" % (
func.func_name, func.func_doc
)
Add comment
Curryng methods, Marko Mikulicic, 2006/03/27
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)
Add comment
Using curry to wrap debugging information around calls, Scott David Daniels, 2001/06/23
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'
Add comment
This isn't currying, David Abrahams, 2004/02/19
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
Add comment
Current curry code, Scott David Daniels, 2005/01/23
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
Add comment
Warning: special case. Change needed above, Scott David Daniels, 2006/01/22
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:
...
Add comment
can i use this code in commercial project?, Peter Bratlovksy, 2006/03/02
Hi. I wanna include code in commercial project, do you mind? :)
http://mortgage-calculator.teach-nology.com
Add comment
|
|
|
|
|
 |
|