Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python currying with any number of variables

I am trying to use currying to make a simple functional add in Python. I found this curry decorator here.

def curry(func):     
    def curried(*args, **kwargs):
        if len(args) + len(kwargs) >= func.__code__.co_argcount:
            return func(*args, **kwargs)
        return (lambda *args2, **kwargs2:
            curried(*(args + args2), **dict(kwargs, **kwargs2)))
    return curried

@curry
def foo(a, b, c):
    return a + b + c

Now this is great because I can do some simple currying:

>>> foo(1)(2, 3)
6
>>> foo(1)(2)(3)
6

But this only works for exactly three variables. How do I write the function foo so that it can accept any number of variables and still be able to curry the result? I've tried the simple solution of using *args but it didn't work.

Edit: I've looked at the answers but still can't figure out how to write a function that can perform as shown below:

>>> foo(1)(2, 3)
6
>>> foo(1)(2)(3)
6
>>> foo(1)(2)
3
>>> foo(1)(2)(3)(4)
10
like image 622
Kevin Avatar asked Aug 06 '16 03:08

Kevin


People also ask

Does Python support Currying?

Python and Curried FunctionsPython does not have an explicit syntax for Currying functions; but it does provide infrastructure that allows Curried functions to be created.

What is the difference between Currying and partial application?

Simple answer. Currying: Lets you call a function, splitting it in multiple calls, providing one argument per-call. Partial Application: Lets you call a function, splitting it in multiple calls, providing multiple arguments per-call.

What is the benefit of Currying?

Currying is helpful when you have to frequently call a function with a fixed argument. Considering, for example, the following function: If we want to define the function error , warn , and info , for every type, we have two options. Currying provides a shorter, concise, and more readable solution.

Is Currying supported in scheme?

It is possible to generate curried functions in Scheme. The function curry2 generates a curried version of a function, which accepts two parameters. The curried version takes one parameter at a time. Similarly, curry3 generates a curried version of a function that takes three parameters.


3 Answers

Arguably, explicit is better than implicit:

from functools import partial

def example(*args):
    print("This is an example function that was passed:", args)

one_bound = partial(example, 1)
two_bound = partial(one_bound, 2)
two_bound(3)

@JohnKugelman explained the design problem with what you're trying to do - a call to the curried function would be ambiguous between "add more curried arguments" and "invoke the logic". The reason this isn't a problem in Haskell (where the concept comes from) is that the language evaluates everything lazily, so there isn't a distinction you can meaningfully make between "a function named x that accepts no arguments and simply returns 3" and "a call to the aforementioned function", or even between those and "the integer 3". Python isn't like that. (You could, for example, use a zero-argument call to signify "invoke the logic now"; but that would break special cases aren't special enough, and require an extra pair of parentheses for simple cases where you don't actually want to do any currying.)

functools.partial is an out-of-box solution for partial application of functions in Python. Unfortunately, repeatedly calling partial to add more "curried" arguments isn't quite as efficient (there will be nested partial objects under the hood). However, it's much more flexible; in particular, you can use it with existing functions that don't have any special decoration.

like image 101
Karl Knechtel Avatar answered Oct 05 '22 03:10

Karl Knechtel


You can implement the same thing as the functools.partial example for yourself like this:

def curry (prior, *additional):
    def curried(*args):
        return prior(*(args + additional))
    return curried

def add(*args):
    return sum(args)

x = curry(add, 3,4,5)
y = curry(b, 100)
print y(200)
# 312

It may be easier to think of curry as a function factory rather than a decorator; technically that's all a decorator does but the decorator usage pattern is static where a factory is something you expect to be invoking as part of a chain of operations.

You can see here that I'm starting with add as an argument to curry and not add(1) or something: the factory signature is <callable>, *<args> . That gets around the problem in the comments to the original post.

like image 32
theodox Avatar answered Oct 05 '22 01:10

theodox


FACT 1: It is simply impossible to implement an auto currying function for a variadic function.

FACT 2: You might not be searching for curry, if you want the function that will be passed to it * to know* that its gonna be curried, so as to make it behave differently.

In case what you need is a way to curry a variadic function, you should go with something along these lines below (using your own snipped):

def curryN(arity, func):
    """curries a function with a pre-determined number of arguments"""
    def curried(*args, **kwargs):
        if len(args) + len(kwargs) >= arity:
            return func(*args, **kwargs)
        return (lambda *args2, **kwargs2:
            curried(*(args + args2), **dict(kwargs, **kwargs2)))
    return curried

def curry(func):
    """automatically curries a function"""
    return curryN(func.__code__.co_argcount, func);

this way you can do:

def summation(*numbers):
    return sum(numbers);

sum_two_numbers = curryN(2, summation)
sum_three_numbers = curryN(3, summation)
increment = curryN(2, summation)(1)
decrement = curryN(2, summation)(-1)
like image 43
Otto Nascarella Avatar answered Oct 05 '22 01:10

Otto Nascarella