Welcome, guest | Sign In | My Account | Store | Cart

The Boost.Bind library http://www.boost.org/libs/bind/bind.html , which I use a lot in C++, has a very nice implementation of the Curry technique. The main innovation of the library is usage of 'placeholders', which allows 'currying' arbitrary parameters in the arg list (please see discussion section). I missed this library in Python, and reimplementing it in a dynamic language was a piece of cake (and I did not have to yell at my compiler to get it done ;). Enjoy!

Python, 74 lines
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
class placeholder(object):
    def __init__(self, pos):
        self.pos = pos

_1 = placeholder(0)
_2 = placeholder(1)
_3 = placeholder(2)

# etc... would be nice to have code to generate N placeholders automatically. 
# is eval the way to do it?
#>>> (Update) see George's comment for an answer to 
#>>> the above question and and alternative solution

def bind(foo, *boundargs, **boundkw):
    """ Allows binding some of the function's args, using placeholders _1, _2,
    etc for the args that are NOT bound (or curried, if you like)
    ========= FOOD FOR DOCTEST ===========
    >>> def foo(a, b, c, d = None, e = None): return (a, b, c, d, e)
    >>> foo(1, 2, 3, e = 'Nooo!')
    (1, 2, 3, None, 'Nooo!')
    >>> foo1 = bind(foo, _2, 2, _1, e = 'Nee!')
    >>> foo1(1, 3)
    (3, 2, 1, None, 'Nee!')
    Test overriding the bound args with the calltime args
    >>> foo1(1, 3, e = 'Overritten')
    (3, 2, 1, None, 'Overritten')
    """
    def with_bound_args(*a, **kw):
        args = []
        for arg in boundargs:
            if isinstance(arg, placeholder):
                args.append(a[arg.pos])
            else:
                args.append(arg)
        #>>> (Update) Peter Harris (PEP 309) mentioned
        #>>> that it makes much more sense to override
        #>>> the bound keyword args with call-time args;
        #>>> hence we have to make a copy of the bound
        #>>> dict and update it, rather than updatinng the 
        #>>> call-time keyword arg dictionary. I agree
        #>>> with the point, although there is an alternative
        #>>> of treating it as an error. The more lenient 
        #>>> way seems more Pythonic.
        kwdict = boundargs.copy()
        kwdict.update(kw)
        return foo(*args, **kwdict)
    return with_bound_args

#>>> Update: I thought about this a little bit longer, especially considering 
#>>> George's observation that this technique is an implementation of 
#>>> 'partial function application' rather that just 'curry'. In languages where
#>>> 'partial application' or 'curry' is supported natively it is possible to 
#>>> just use the name of the function to create a function with partially 
#>>> applied agruments. Well, why not do it in Python? All it takes is a 
#>>> decorator. The decorator decides whether a regular call or a partial 
#>>> application is desired by looking for placeholders in the argument list.

def partial_application(foo):
    def inner(*a, **kw):
        if True in [isinstance(o, placeholder) for o in a]:
            return bind(foo, *a, **kw)
        else: return foo(*a, **kw)
    return inner

@partial_application
def pa_test(a, b, c, d, e = None, f = None):
    """ ============ DOCTEST FOOD ============
    >>> pa_test(1, 2, 3, 4, 5)
    (1, 2, 3, 4, 5, None)
    >>> pa = pa_test(_1, 2, 3, 4, 5)
    >>> pa(1)
    (1, 2, 3, 4, 5, None)
    """
    return (a, b, c, d, e, f)

As you can tell from the docstring, the usage of bind given

def foo(a, b, c, d = None, e = None): return (a, b, c, d, e) could be something like

foo1 = bind(foo, _2, 2, _1, e = 'Nee!')

where _1, _2, ... _n stand for the UNcurried parameters, indexed by their position (the first index is _1, following the Boost.Bind convention) and the rest of parameters are curried.

Now when foo1 is called with (1, 3), these parameters are matched with placeholders by the placeholders' position value. I intentionally introduced placeholders starting from the greater one to demonstrate the power of this technique. When foo1 is invoked with (1, 3) the result is (3, 2, 1, None, 'Nee!'), with 3 coming before 1.

Another difference from the C++ version besides the implementation is the handling of keyword args, which is pretty self-explanatory.

Similar recipes: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52549 http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/363775 http://www.xoltar.org/languages/python/partial.py (quite interesting, from a cursory reading this appears to implement yet even more general form of the placeholder-like technique) http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52564

I also discovered that partial application has been accepted as a PEP! See here: http://www.python.org/peps/pep-0309.html

If anyone is interested, my python-oriented blog is @ http://pythonzweb.blogspot.com

1 comment

George Sakkis 16 years, 2 months ago  # | flag

Nice recipe; Boost.Bind is actually a generalization of partial application so it's even more powerful. Two comments:

  • kw.copy() is not necessary; when a dict d is passed in as named arguments, a copy is created anyway.

  • You can easily create automatically the first N placeholders; no need for eval():

    _g = globals() for i in xrange(100): _g['_%d' % (i+1)] = placeholder(i)

Although for practical reasons this is more than enough, I still don't like the idea of predefining N. With a small change to the placeholder's syntax to enclose the position in brackets, placeholders are constructed and cached only when necessary:

class _PlaceHolders(object):
    _cache = {}
    def __getitem__(self, i):
        assert isinstance(i,int) and i>0
        try: return self._cache[i]
        except KeyError:
            return self._cache.setdefault(i,placeholder(i-1))

_ = _PlaceHolders()

def foo(a, b, c, d = None, e = None): return (a, b, c, d, e)
foo1 = bind(foo, _2, 2, _1, e = 'Nee!')
foo2 = bind(foo, _[2], 2, _[1], e = 'Nee!')
assert foo1(3,4) == foo2(3,4)
Created by Maxim Khesin on Wed, 14 Sep 2005 (PSF)
Python recipes (4591)
Maxim Khesin's recipes (2)

Required Modules

  • (none specified)

Other Information and Tasks