My limited understanding is such:

1) Partial Function Application

Partial Function Application is the process of returning a function that takes a lesser number of arguments. If you provide 2 out of 3 arguments, it'll return a function that takes 3-2 = 1 argument. If you provide 1 out of 3 arguments, it'll return a function that takes 3-1 = 2 arguments. If you wanted, you could even partially apply 3 out of 3 arguments and it would return a function that takes no argument.

So given the following function:

f(x,y,z) = x + y + z;

When binding 1 to x and partially applying that to the above function f(x,y,z) you'd get:

f(1,y,z) = f'(y,z);

Where: f'(y,z) = 1 + y + z;

Now if you were to bind y to 2 and z to 3, and partially apply f'(y,z) you'd get:

f'(2,3) = f''();

Where: f''() = 1 + 2 + 3 ;

Now at any point, you can choose to evaluate f , f' or f'' . So I can do:

print(f''()) // and it would return 6;

or

print(f'(1,1)) // and it would return 3;

2) Currying

Currying on the other hand is the process of splitting a function into a nested chain of one argument functions. You can never provide more than 1 argument, it's one or zero.

So given the same function:

f(x,y,z) = x + y + z;

If you curried it, you would get a chain of 3 functions:

f'(x) -> f''(y) -> f'''(z)

Where:

f'(x) = x + f''(y); f''(y) = y + f'''(z); f'''(z) = z;

Now if you call f'(x) with x = 1 :

f'(1) = 1 + f''(y);

You are returned a new function:

g(y) = 1 + f''(y);

If you call g(y) with y = 2 :

g(2) = 1 + 2 + f'''(z);

You are returned a new function:

h(z) = 1 + 2 + f'''(z);

Finally if you call h(z) with z = 3 :

h(3) = 1 + 2 + 3;

You are returned 6 .

3) Closure

Finally, Closure is the process of capturing a function and data together as a single unit. A function closure can take 0 to infinite number of arguments, but it's also aware of data not passed to it.

Again, given the same function:

f(x,y,z) = x + y + z;

You can instead write a closure:

f(x) = x + f'(y, z);

Where:

f'(y,z) = x + y + z;

f' is closed on x . Meaning that f' can read the value of x that's inside f .

So if you were to call f with x = 1 :

f(1) = 1 + f'(y, z);

You'd get a closure:

closureOfF(y, z) = var x = 1; f'(y, z);

Now if you called closureOfF with y = 2 and z = 3 :

closureOfF(2, 3) = var x = 1; x + 2 + 3;

Which would return 6

Conclusion

Currying, partial application and closures are all somewhat similar in that they decompose a function into more parts.

Currying decomposes a function of multiple arguments into nested functions of single arguments that return functions of single arguments. There's no point in currying a function of one or less argument, since it doesn't make sense.

Partial application decomposes a function of multiple arguments into a function of lesser arguments whose now missing arguments were substituted for the supplied value.

Closure decomposes a function into a function and a dataset where variables inside the function that were not passed in can look inside the dataset to find a value to bind against when asked to evaluate.

What's confusing about all these is that they can kind of each be used to implement a subset of the others. So in essence, they're all a bit of an implementation detail. They all provide similar value in that you don't need to gather all values upfront and in that you can reuse part of the function, since you've decomposed it into discreet units.

Disclosure

I'm by no means an expert of the topic, I've only recently started learning about these, and so I provide my current understanding, but it could have mistakes which I invite you to point out, and I will correct as/if I discover any.