Currying is a technique in functional programming to transform a function with multiple arguments into a sequence of nested functions with fewer arguments.

The number of arguments to a function is also called arity.

In other words, currying is a process to reduce the arity of some callable f(a, b, c), by translating it to a sequence of callables as f(a)(b)(c).

{{...}}

As an example, we can translate the below function with 3 arguments

# function
def add(a, b, c):
	return a+b+c

# fn call
add(1, 2, 3) # 6

to a nested sequence of 3 functions (with one argument each):

# curried function
def add(a):
	def _n1(b):
		def _n2(c):
			return a+b+c
		return _n2
	return _n1

#fn call
add(1)(2)(3) # 6

Let's unravel add(1)(2)(3) to understand it better:

_obj1 = add(1)
_obj2 = _obj1(2)
result = _obj2(3)
print(result) # 6

Here, _obj1 holds the function returned by add (return _n1):

# _obj1
def _n1(b):
	def _n2(c):
		return a+b+c
	return _n2

_obj2 holds the function returned by _obj1 (return _n2):

def _n2(c):
	return a+b+c

When _obj2 is called with the third argument (3), it calculates the result using its parameters (c = 3) and the parameters passed to its parent functions (a = 1 & b = 2).

How does it access these outer parameters? Being a nested function, _n2 has access to the variable scope of the outer functions _n1 and add.

...
def _n2(c):
	print(locals()) # {'c': 3, 'a': 1, 'b': 2}
	...
...

Partial Application

Using currying, we can get partially evaluated functions. As in, we can store intermediate results of partial functions. This provides many benefits like interim analysis, saving checkpoint states, "resume" capability, etc.

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

# partial function
add_7 = lambda x: add(x, 7)
add_7(3) # 10