Partial application and closures
Asked Answered
H

5

13

I was asked what's the relationship between partial function application and closures. I would say there isn't any, unless I'm missing the point. Let's say I'm writing in python and I have a very simple function MySum defined as follows:

MySum = lambda x, y : x + y;

Now I'm fixing one parameter to obtain a function with smaller arity which returns the same value that MySum would return if I called it with the same parameters (partial application):

MyPartialSum = lambda x : MySum(x, 0);

I could do the the very same thing with C:

int MySum(int x, int y) { return x + y; }
int MyPartialSum(int x) { return MySum(x, 0); }

So, the dumb question is: what's the difference? Why should I need closures for partial applications? These codes are equivalent, I don't see what's the bound with closures and partial application.

Hickok answered 21/7, 2012 at 8:57 Comment(0)
N
6

Partial application is a technique whereby you take an existing function and a subset of it's arguments, and produce a new function that accepts the remaining arguments.

In other words, if you have function F(a, b), a function that applies partial application of a would look like B(fn, a) where F(a, b) = B(F, a)(b).

In your example you're simply creating new functions, rather than applying partial application to the existing one.

Here's an example in python:

def curry_first(fn, arg):
    def foo(*args):
       return fn(arg, *args)
    return foo

This creates a closure over the supplied function and argument. A new function is returned that calls the first function with new argument signature. The closure is important - it allows fn access to arg. Now you can do this sort of thing:

add = lambda x, y : x + y;
add2 = curry_first(add, 2)
add2(4) # returns 6

I've usually heard this referred to as currying.

Newsboy answered 21/7, 2012 at 9:7 Comment(6)
I've tried to clarify things by reading a lot of stuff on the matter: there's a lot of confusion between currying and partial applications, which are commonly interchanged but are different things. Wikipedia says: "partial application (or partial function application) refers to the process of fixing a number of arguments to a function, producing another function of smaller arity"Hickok
Which is exactly what is happening above. As far as I can tell, currying is just a particular expression of the form.Newsboy
Yes, I see, your code does that, and it's pretty much clear. But... doesn't mine do it too? Differently, it's obvious, I'm not passing functions as parameters, but I'm fixing an argument too and creating a function with smaller arity which does the same thing and returns the same value as if I called the original function with all the parameters. Sorry, I feel pretty dumb, I'm puzzled...Hickok
Perfect, so this is what I was trying to do (only lambdas here): Sum = lambda x, y : x + y; Curry = lambda fn, arg : lambda *args : fn(arg, *args); PartialSum = Curry(Sum, 2); PartialSum(4);Hickok
Yep, that's a nice short example :DNewsboy
@Newsboy Currying is not the same thing. Currying will return a nested chain of functions at each step, whereas partial application returns a function that takes the missing arguments in which the arguments already provided were replaced by the value provided.Quack
C
9

Partial function application is about fixing some arguments of a given function to yield another function with fewer arguments, like

sum = lambda x, y: x + y
inc = lambda x: sum(x, 1)

Notice that 'inc' is 'sum' partially applied, without capturing anything from the context (as you mentioned closure).

But, such hand-written (usually anonymous) functions are kind of tedious. One can use a function factory, which returns an inner function. The inner function can be parameterized by capturing some variable from its context, like

# sum = lambda x, y: x + y
def makePartialSumF(n):
    def partialSumF(x):
        return sum(x, n)
    return partialSumF

inc = makePartialSumF(1)
plusTwo = makePartialSumF(2)

Here the factory makePartialSumF is invoked twice. Each call results in a partialSumF function (capturing different values as n). Using closure makes the implementation of partial application convenient. So you can say partial application can be implemented by means of closure. Of course, closures can do many other things! (As a side node, python does not have proper closure.)

Currying is about turning a function of N arguments into a unary function which returns a unary function... for example we have a function which takes three arguments and returns a value:

sum = lambda x, y, z: x + y + z

The curried version is

curriedSum = lambda x: lambda y: lambda z: x + y + z

I bet you wouldn't write python code like that. IMO the motivation of Currying is mostly of theoretical interest. (A framework of expressing computations using only unary functions: every function is unary!) The practical byproduct is that, in languages where functions are curried, some partial applications (when you 'fix' arguments from the left) are as trivial as supplying arguments to curried function. (But not all partial applications are as such. Example: given f(x,y,z) = x+2*y+3*z, when you binds y to a constant to yield a function of two variables.) So you can say, Currying is a technique which, in practice and as a byproduct, can make many useful partial functional applications trivial, but that's not the point of Currying.

Cecilia answered 11/10, 2012 at 19:13 Comment(1)
In short: currying is related to how it is declared, whilst partial application is related to how it is used.Thought
N
6

Partial application is a technique whereby you take an existing function and a subset of it's arguments, and produce a new function that accepts the remaining arguments.

In other words, if you have function F(a, b), a function that applies partial application of a would look like B(fn, a) where F(a, b) = B(F, a)(b).

In your example you're simply creating new functions, rather than applying partial application to the existing one.

Here's an example in python:

def curry_first(fn, arg):
    def foo(*args):
       return fn(arg, *args)
    return foo

This creates a closure over the supplied function and argument. A new function is returned that calls the first function with new argument signature. The closure is important - it allows fn access to arg. Now you can do this sort of thing:

add = lambda x, y : x + y;
add2 = curry_first(add, 2)
add2(4) # returns 6

I've usually heard this referred to as currying.

Newsboy answered 21/7, 2012 at 9:7 Comment(6)
I've tried to clarify things by reading a lot of stuff on the matter: there's a lot of confusion between currying and partial applications, which are commonly interchanged but are different things. Wikipedia says: "partial application (or partial function application) refers to the process of fixing a number of arguments to a function, producing another function of smaller arity"Hickok
Which is exactly what is happening above. As far as I can tell, currying is just a particular expression of the form.Newsboy
Yes, I see, your code does that, and it's pretty much clear. But... doesn't mine do it too? Differently, it's obvious, I'm not passing functions as parameters, but I'm fixing an argument too and creating a function with smaller arity which does the same thing and returns the same value as if I called the original function with all the parameters. Sorry, I feel pretty dumb, I'm puzzled...Hickok
Perfect, so this is what I was trying to do (only lambdas here): Sum = lambda x, y : x + y; Curry = lambda fn, arg : lambda *args : fn(arg, *args); PartialSum = Curry(Sum, 2); PartialSum(4);Hickok
Yep, that's a nice short example :DNewsboy
@Newsboy Currying is not the same thing. Currying will return a nested chain of functions at each step, whereas partial application returns a function that takes the missing arguments in which the arguments already provided were replaced by the value provided.Quack
L
3

Simply, the result of a partial application is normally implemented as a closure.

Lifesaver answered 30/9, 2013 at 13:32 Comment(0)
R
2

Closures are not a required functionality in a language. I'm experimenting a homemade language, lambdatalk, in which lambdas don't create closures but accept partial application. For instance this is how the set [cons, car, cdr] could be defined in SCHEME:

(def cons (lambda (x y) (lambda (m) (m x y))))
(def car (lambda (z) (z (lambda (p q) p))))
(def cdr (lambda (z) (z (lambda (p q) q))))

(car (cons 12 34)) -> 12
(cdr (cons 12 34)) -> 34

and in lambdatalk:

{def cons {lambda {:x :y :m} {:m :x :y}}} 
{def car {lambda {:z} {:z {lambda {:x :y} :x}}}}
{def cdr {lambda {:z} {:z {lambda {:x :y} :y}}}}

{car {cons 12 34}} -> 12
{cdr {cons 12 34}} -> 34

In SCHEME the outer lambda saves x and y in a closure the inner lambda can access given m. In lambdatalk the lambda saves :x and :y and returns a new lambda waiting for :m. So, even if closure (and lexical scope) are usefull functionalities, there are not a necessity. Without any free variables, out of any lexical scope, functions are true black boxes without any side effect, in a total independance, following a true functional paradigm. Don't you think so?

Ruthy answered 18/2, 2017 at 12:58 Comment(0)
D
0

For me, using the partialSum that way, makes sure that you only depend on one Function to sum the numbers (MySum) and that will make debugging a lot more easier if things go wrong, because you would not have to worry about the logic of your code in two different parts of your code.

If in the future you decide to change the logic of MySum, (say for example, make it return x+y+1) then you will not have to worry about MyPartialSum because it calls MySum

Even if it seems stupid, having code written that way is just to simplify the process of dependencies in functions. I am sure you will notice that later in your studies.

Dribble answered 21/7, 2012 at 9:5 Comment(5)
I see. Anyway I could do this regardless of the language I'm using is supporting closures or not. Closures are pretty useless in this caseHickok
This is just 'writing code' though. Partial application should return a function, not a result.Newsboy
Should it? That's very interesting! So if I defined: MyPartialSum = lambda x : lambda x : MySum(x, 0) that would be "true" partial application? This isn't much clear to meHickok
Now you've just written a new function that copies the body of the first function. Partial application should take a function, apply the partial arguments and return a new function. That's why it is call partial application, because it applies the partial arguments to the function.Newsboy
Ok, that's what I was missing. If it's true it should return a function, that is the point.Hickok

© 2022 - 2024 — McMap. All rights reserved.