DEV Community

Fernando Martínez González
Fernando Martínez González

Posted on

Curried functions in Python

Let's create curried functions in Python!

You can search for curried functions on the internet and you will find very scary things Wiki. Some languages like Haskell have this functionality defined at the core and its something everyone plays on his favorite language when they enter the functional world.

Let's define what we want, not why we want it, because that's not important, right? We all want curried functions!

Curried function

A curried function is a function that only receives one argument.

def add2(x):
    return x + 2

But, what if we want to create a function that receives more than one argument? Well, curried functions can return any value, including another function, preferably a curried one.

def add(x):
    def add_x(y):
        return x + y
    return add_x

But, what if we want to add even more arguments? It gets pretty ugly when the number of arguments that our function needs increases.

Let's try to deal with this with a more general implementation.

How do we use this function? Now the syntax for a function call is a little bit different and it can be strange. On the other hand, we now can get one "partially" evaluated function.

add(2)(3)  # 5
add2 = add(2)  # function
add2(3)  # 5

This comes handy when we use map or filter

def greater_than(x):
    def greater_than_x(y):
        return y > x
    return greater_than_x

list(
    map(
        add(2),
        filter(
            greater_than(5),
            range(10)
        )
    )
)  # [8, 9, 10, 11]

Ideally, we want to keep the nice syntax for function calls that Python has when we don't want to use the curried possibility.

Proposal

Let's define a decorator that takes a normal python function and returns a curried version of it. We will define our function as usual and we will get both ways of calling it.

@curried
def add(x, y):
    return x + y

add2 = add(2)  # function
add2(3)  # 5
add(2, 3)  # 5

This is a possible implementation of this curried decorator.

from inspect import signature, Parameter


def curried(func):
    sig = signature(func)
    params = sig.parameters.values()

    assert all(map(is_positional, params)), \
        'All parameters should be positional'

    def curried_func_first_call(*args):
        N = len(params)
        applied = list(args)

        def curried_func(*args):
            assert len(applied) + len(args) <= N, \
                'Too many arguments, expected {}'.format(N)

            applied.extend(args)

            if len(applied) == N:
                return func(*applied)

            return curried_func

        if len(applied) == N:
            return func(*applied)

        return curried_func

    return curried_func_first_call


def is_positional(param):
    return param.kind == Parameter.POSITIONAL_ONLY or \
        param.kind == Parameter.POSITIONAL_OR_KEYWORD

What about partial

Probably everyone has heard about partial function from the module functools. But this does not behave entirely as we want. It does a lot more than we are doing here, but I was interested in syntax.

One of the problems is that partial always returns a function, even when all the arguments were supplied.

def add(x, y):
    x + y

f = partial(add, 2, 3)  # function
f()  # 5

Furthermore, partial does not curry the resulting function

def add(x, y, z):
    return x + y + z

f = partial(add, 1)

f(2)  # TypeError: add() missing 1 required positional argument: 'z'
f(2, 3)  # 6

Top comments (0)