PEP 309 (Partial Function Application) Idea

Chris Perkins chrisperkins99 at gmail.com
Sat Mar 12 14:24:07 EST 2005


>Scott David Daniels wrote:
>>>Chris Perkins wrote:
>>>>Random idea of the day: How about having syntax support for
>>>>currying/partial function application, like this:
>>>>func(..., a, b)
>>>>func(a, ..., b)
>>>>func(a, b, ...)
>>>>
>>>>That is:
>>>>1) Make an Ellipsis literal legal syntax in an argument list.
>>>>2) Have the compiler recognize the Ellipsis literal and transform
>>>>   the function call into a curried/parially applied function.
>>>>So the compiler would essentially do something like this:
>>>>
>>>>func(a, ...) ==> curry(func, a)
>>>>func(..., a) ==> rightcurry(func, a)
>>>>func(a, ..., b) ==> rightcurry(curry(func,a), b)
>
>The interaction of this with keyword args and omitted args is
>problematic (as is the case for rightcurry, in fact).  I can't
>think of a good way to explain what _should_ happen for a
>function defined as def function(*args, **kwargs): ... when you:
>
>     def fun(bug, frog, verb): ...
>     f1 = function(1, ..., frog=3)
>     f2 = f1(..., 4)
>     f2()
>
>Keywords were why I did no rightcurry definition in the first place;
>I couldn't convince myself there was a good, obvious, resolution.
>

I agree that it's not obvious what _should_ happen in complex cases.

I wrote up a psuedo-implementation to play with, just to get a feel
for it.  It works only on @curryable functions, and I use "__" in place
of "...". I think that's about as close as I can get in pure Python.

def curryable(func):
    "This hurts my brain a little bit, but I _think_ it works."
    def proxyfunc(*args, **kwds):
        if list(args).count(Ellipsis) > 1:
            raise TypeError('Your mother was a hampster...')
        if Ellipsis in args:
            @curryable
            def curried(*a, **k): 
                kwdict = kwds.copy()
                kwdict.update(k)
                epos = list(args).index(Ellipsis)
                return func(*(args[:epos] + a + args[epos+1:]), **kwdict)
            return curried
        return func(*args, **kwds)
    return proxyfunc

def test():
    __ = Ellipsis
    @curryable
    def fun(bug, frog, verb):
        print bug, frog, verb
        
    f1 = fun(1, __, frog=3)
    f2 = f1(__, 4)
    try:
        f2()
    except TypeError, e:
        print e # multiple values for keyword 'frog'
        
    f1 = fun(1, __, verb=3) 
    f2 = f1(__, 4) 
    f2()
    f2(verb=99)
    
    try:
        f1(__, 2, __)
    except TypeError, e:
        print e

if __name__ == '__main__':
    test()


After playing with this a bit, I found the semantics to be reasonably 
obvious once I got used to it; at least in the cases I tried.  I'd be
quite happy to have anything fishy throw an exception.  Cases where 
I would actually use this would be quite simple, I think.

On the other hand, I'm not convinced that this construct would be
especially useful.  I spent some time looking at uses of lambda in the
standard library, hoping to find lots of examples where a (...)
function would be more readable, but I eventually gave up. (Found
dozens of cases where a list-comp or genexp would be better, though).

While I think that func(x, ...) is more readable than partial(func, x),
I'm not sure that I would use either of them often enough to warrant
special syntax. If anyone disagrees, feel free to petition python-dev;
I don't expect to pursue this any further.

Thanks to all for your comments.


Chris Perkins



More information about the Python-list mailing list