"""Model of currie()ing. The crucial thing is that currie behaves *as if* it were defined by Code: def currie(func, *prior, **named): def result(*args, **what, func=func, prior=prior, given=named): what.update(given) return apply(func, prior + args, what) return result but without the complaints about parameters after the *-items. What I'm suggesting for python2 gives meaning to this that lets us tunnel the data result needs in from currie. Without that tunnelling, I would need currie() to be a built-in: but I find this `closure' tunnel to be more pythonic than currie(), which is an unashamedly functional tool. What follows is a suggestion for how to implement currie in python 1.5 to explore what behaviour it should exhibit. There remains an interesting question: should currie(currie(f, a=0), a=None)() invoke * f(a=0) or * f(a=None) ? This amounts to a choice about how keyword arguments to currie are understood: either * as changes to the defaults f stipulates for some of its keyword arguments * as supplying the value to be used when f is called The former, a=None, gives precedence to the final invocation of the curried function; the latter, a=0, involves currie(f, a=0) `using up' f's ability to take the keyword argument. Since currie's handling of positional arguments, *args, `uses up' f's ability to accept its early (positional) arguments, I lean towards the latter: that is what I have used in the def above and implemented below, though a comment says how to do the former. (The code is so structured that the choice is encapsulated in one place, though it is used in two. See currie.__keywordjoin's doc.) If the implementation of python provided access to the closures, e.g. as an object with a read-only namespace called, say, .func_init, then we could enhance the above to contract out multiple-currie()ing in the same way as the class implementation, e.g. Code: def currie(func, *prior, **named): try: old = func.func_init p, n, f, c = old.prior, old.given, old.func, old.__creator except AttributeError: pass else: if c is currie: # or some kindred check try: p = p + prior # be sure this succeeds before changing named named.update(n) # be sure that succeeds before changing prior except (AttributeError, TypeError): pass else: # we have changed named, so now we must change prior and func: prior, func = p, f def result(*args, **what, func=func, prior=prior, given=named, __creator=currie): what.update(given) return apply(func, prior + args, what) return result $Id: currie.py,v 1.1 2001/10/17 18:00:19 eddy Exp $ """ class currie: def __init__(self, func, *args, **what): if isinstance(func, currie): args = func.__tuple + args what = func.__keywordjoin(what) func = func.__function # data which won't be changed: self.__function = func self.__tuple = args self.__book = what def __keywordjoin(self, what): """Private method: combines keyword args of init and call. Single argument (other than self) is a dictionary which we are at liberty to modify (i.e. because it is the **what of a call). Result is a dictionary combining this one with the keyword args passed to currie when self was created. Exists so that __init__'s distangling of currie instances matches what invocation, i.e. __call__, does: currie(currie(f, a=7), a=8) should behave as if no distangling were done - as if the outer currie's instance really invoked that of the inner, rather than deploying a short-cut. The crucial choice is as to whether args passed at initialisation of the currie instance take precedence over or are over-ridden by args passed to invocation. This method exists to ensure that __init__ and __call__ behave consistently. See also: doc string of currie.py\n""" # Occam's choice: what.update(self.__book) return what # Alternatively: ans = self.__book.copy(); ans.update(what); return ans def __call__(self, *args, **what): return apply(self.__function, self.__tuple + args, self.__keywordjoin(what)) # What follows is icing, an aside to the general spec of currie # Representation and string (incidental details): def __repr__(self): try: ans = self.__repr except AttributeError: ans = self.__repr = self.__string(repr) return ans def __str__(self): try: ans = self.__str except AttributeError: ans = self.__str = self.__string(str) return ans def __string(self, stringer): rep = stringer(self.__function) if rep[0] == '<' and rep[-1] == '>': rep = rep[1:-1] if self.__tuple: rep = rep + ' curried with ' + stringer(self.__tuple) if self.__book: if self.__tuple: join = ' and ' else: join = ' curried with ' rep = rep + join + stringer(self.__book) return '<' + rep + '>' # May as well give the poor thing a doc-string (lazily). __doctemplate = """Curried function: %s This is callable. Arguments passed to a call of it are combined with those curried into it already: the combination is passed to the base function. Thus, with func as %s prior as %s named as %s apply(self, args, book) performs book.update(named) and returns apply(func, prior + args, book).\n""" __docfuncdoctemplate = """ The documentation for %s says: """ def __getattr__(self, key): if key == '__doc__': ans = self.__doctemplate % ( self, self.__func, self.__tuple, self.__book) ps = self.__func.__doc__ if ps: ans = ans + self.__docfuncdoctemplate % self.__func + ps + '\n' self.__doc__ = ans # so we won't be asked for this again return ans raise AttributeError, key _rcs_log = """ $Log: currie.py,v $ Revision 1.1 2001/10/17 18:00:19 eddy added in all files previously missing from RCS Revision 1.4 2000/01/02 18:41:46 eddy Tweaks. Revision 1.3 1999/12/22 22:34:40 eddy The closure revolution. Yummmm. Revision 1.2 1999/12/18 18:17:25 eddy Tweaks. Initial Revision 1.1 1999/12/16 22:35:25 eddy """