7

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
Kevin
  • 977
  • 1
  • 10
  • 17
  • @curry def foo(*arg): return sum(arg). Try This – Rakesh Kumar Aug 06 '16 at 03:26
  • 4
    How would the currying know when to stop? If the `foo` can take any number of arguments, does `foo(1, 2, 3)` invoke it or does it curry it so more arguments can be added? – John Kugelman Aug 06 '16 at 03:29
  • I've tried sum(args), it doesn't work since after the first call, foo becomes an int and can't be called again, so essentially the currying doesn't work. – Kevin Aug 06 '16 at 03:44

4 Answers4

8

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.

Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153
  • "there will be nested partial objects under the hood" I don't think this is true: `print(functools.partial(f, 1))` and `print(functools.partial(functools.partial(f, 1), 2))` Respectively output: `functools.partial(, 1)` and `functools.partial(, 1, 2)` See how the function stored is twice the same, `functools.partial` copies the function and args from the input partial (from what is seems). Since it [doesn't store much else](https://docs.python.org/2/library/functools.html#partial-objects) nesting these calls should beok. – cglacet Feb 24 '19 at 17:25
  • I could have sworn I saw it nest the partial objects in an earlier version of Python. This seems like the sort of thing that gets incrementally improved all the time with relatively few people noticing :) – Karl Knechtel Sep 01 '19 at 14:17
1

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.

theodox
  • 12,028
  • 3
  • 23
  • 36
1

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)
Otto Nascarella
  • 2,445
  • 2
  • 17
  • 8
1

I think this is a decent solution:

from copy import copy
import functools


def curry(function):

  def inner(*args, **kwargs):
    partial = functools.partial(function, *args, **kwargs)
    signature = inspect.signature(partial.func)
    try:
      signature.bind(*partial.args, **partial.keywords)
    except TypeError as e:
      return curry(copy(partial))
    else:
      return partial()

  return inner

This just allow you to call functools.partial recursively in an automated way:

def f(x, y, z, info=None):
  if info:
    print(info, end=": ")
  return x + y + z

g = curry_function(f)
print(g)
print(g())
print(g(2))
print(g(2,3))
print(g(2)(3))
print(g(2, 3)(4))
print(g(2)(3)(4))
print(g(2)(3, 4))
print(g(2, info="test A")(3, 4))
print(g(2, info="test A")(3, 4, info="test B"))

Outputs:

<function curry.<locals>.inner at 0x7f6019aa6f28>
<function curry.<locals>.inner at 0x7f6019a9a158>
<function curry.<locals>.inner at 0x7f6019a9a158>
<function curry.<locals>.inner at 0x7f6019a9a158>
<function curry.<locals>.inner at 0x7f6019a9a0d0>
9
9
9
test A: 9
test B: 9
cglacet
  • 8,873
  • 4
  • 45
  • 60