Adventures in Currying
Nick Mathewson
nickm at mit.edu
Sun May 14 15:47:09 EDT 2000
I've noticed that about 2/3 of the times I use lambda in Python, I'm
really just asking for curried functions.[*] With this in mind, I
started writing up currying operators of various degrees of
complexity. I give them below, in their approximate evolutionary
order. They are all classes designed to turn python functions into
curried functions.
They're all tested with 1.5.2, but rely on undocumented attributes of
function and code objects. Therefore, they may break on 1.6, and are
certain to break in JPython.
[*] For more information on currying, look on deja.com for a thread
called 'In defence of the two-namespace rule'.
=================================================================
from types import *
if __name__ == '__main__':
# This is our usual example.
def parrot(person, thing, act, stuff, place):
print "Listen, %s! This %s has ceased to %s!" % (person, thing, act)
print "Bereft of %s, it rests in %s!" % (stuff,place)
def fermat(thing1,thing2,act='contain it'):
print "I have discovered the most wonderful %s for this," %thing1
print "but the %s is not large enough to %s." %(thing2,act)
class Curry1:
"""Simplest version.
Doesn't understand about keyword or default arguments.
Forces you to fill in all the arguments at once."""
def __init__(self, fn, *args):
self.fn = fn
self.args = args
def __call__(self, *args):
return apply(self.fn,self.args + args)
if __name__ == '__main__':
print "===1"
Curry1(parrot, "matey", "parrot", "be")("life", "peace")
print
Curry1(Curry1(parrot, "matey"), "parrot", "be")("life", "peace")
print
class Curry2:
"""Slightly more complex, and closer to real currying.
Still doesn't understand about keyword arguments."""
def __init__(self, fn, *args):
if isinstance(fn, Curry2):
self.fn = fn.fn
self.n_args = fn.n_args
self.args = fn.args + args[0]
return
if type(fn) == FunctionType:
code = fn.func_code
self.n_args = code.co_argcount
elif type(fn) == MethodType:
code = fn.im_func.func_code
self.n_args = code.co_argcount
else:
assert None
self.fn = fn
self.args = args
def __call__(self, *args):
if len(args) + len(self.args) >= self.n_args:
return apply(self.fn, self.args + args)
else:
return Curry2(self, args)
if __name__ == '__main__':
print "===2"
Curry2(parrot, "buddy")("python", "slither")("scales")("Amsterdam")
print
class Curry3:
"""Like Curry2, but more complex. Understands keyword arguments. Baffled
by default arguments. Doesn't work on builtins."""
def __init__(self, fn, *args, **kwargs):
if isinstance(fn, Curry3):
self.fn = fn.fn
self.argnames = fn.argnames
self.kwargs = fn.kwargs.copy()
else:
self.fn = fn
self.kwargs = {}
self.argnames = _argNames(fn)
if kwargs:
self.kwargs.update(kwargs)
argnames = self.argnames
for i in range(len(args)):
self.kwargs[argnames[i]] = args[i]
argnames2 = []
for name in argnames[len(args):]:
if not self.kwargs.has_key(name):
argnames2.append(name)
self.argnames = argnames2
def __call__(self, *args, **kwargs):
more = apply(Curry3, (self,)+ args, kwargs)
if len(more.argnames) == 0:
return apply(more.fn, (), more.kwargs)
else:
return more
def _argNames(fn):
if getattr(fn,'argnames',None) is not None:
return fn.argnames
elif type(fn) == FunctionType:
code = fn.func_code
return code.co_varnames[:code.co_argcount]
elif type(fn) == MethodType:
code = fn.im_func.func_code
return code.co_varnames[1:code.co_argcount]
else:
assert None
if __name__ == '__main__':
print "===3"
p = Curry3(parrot, 'matey', act='be')
p('parrot','life')('peace')
print
p('python','scales')(place='peace')
print
p('fish',act='flop')('water',person='Jacques')
class Curry4:
"""Like Curry3, but more complex. Understands default arguments.
Doesn't work on builtins.
Can anybody make this less inefficient?"""
def __init__(self, fn, *args, **kwargs):
if isinstance(fn, Curry4):
self.fn = fn.fn
self.argnames = fn.argnames
self.kwargs = fn.kwargs.copy()
else:
self.fn = fn
self.argnames = _argNames(fn)
self.kwargs = _defaultArgDict(fn,self.argnames)
if kwargs:
self.kwargs.update(kwargs)
argnames = self.argnames
for i in range(len(args)):
self.kwargs[argnames[i]] = args[i]
argnames2 = []
for name in argnames[len(args):]:
if not self.kwargs.has_key(name):
argnames2.append(name)
self.argnames = argnames2
def __call__(self, *args, **kwargs):
more = apply(Curry4, (self,)+ args, kwargs)
if len(more.argnames) == 0:
return apply(more.fn, (), more.kwargs)
else:
return more
def _defaultArgDict(fn,argnames):
if type(fn) == MethodType:
fn = fn.im_func
d = {}
defs = fn.func_defaults
for i in range(-1,-len(defs)-1,-1):
d[argnames[i]] = defs[i]
return d
if __name__ == '__main__':
print "===4"
Curry4(fermat)('proof')('margin')
print
Curry4(fermat)(thing2='available memory')('syntax',act='parse it')
=================================================================
Yes-I-Know-It-Should-Be-Called-Schoenfinkeling'ly yours,
--
Nick Mathewson <nickm at mit.edu> http://www.mit.edu/~nickm/
More information about the Python-list
mailing list