In a previous blog-post I wrote about "currying", a related concept. I posted that currying is the process of implementing a multi-argument function as a combination of single-argument functions, and calls to them. I'll discuss the difference and relationship of Partial Application and Currying, briefly.
Partial Application is something your program can do. "Currying" is something you, or your compiler or interpreter does. So PA seems like a more straightforward concept to implement. And yes, there is an accompanying code-file that does it for you.
Let's start by asking: If we had an implementation of the function 'applyPartially()' or applyP() for short, how would we use it? Let's write some test-cases! Ah, this is Test-Driven-Programming, we write the test-cases first? Well maybe something like that. But let's start by writing the function applyP(), a 'dummy' version of it first. That dummy version says something about what it should do when it grows up, what kind of arguments it expects etc.
function applyP (baseFunction, a, b, c, d, e)
{
/** ... NOT READY
Return a function that is like the argument
'aFunction' except that its leading arguments
will be "fixed" to be those given by the rest
of MY arguments. So, my result is a function
which will (typically) take FEWER arguments
than my 1st argument, 'aFunction'.
*/
...
}
The 1st argument of applyP( ) is called baseFunction because the result of applyP() will be derived from it.
Now let's write one test for it:
function f2(x,y)
{return x + y.
}
var f2b = applyP (f2, 1);
ok (f2b (2) == 3);
Above we partially apply f2 with '1' as the fixed value of its first argument, and get a function f2b as the result. Then we call f2b() passing only one argument '2' to it. Then verify that the result is 3 with our simple test-utility function 'ok()'. There, almost done. Should we go further? No, unless we can answer the following question:
What is it good for? Absolutely nothing? No. Partial Application is good whenever some arguments of a function come from a small fixed set. That usually means you will be calling the function many times with the same values from that fixed set. In such a case it makes sense to partially apply the function to those frequently used values. That will give you new functions that are simpler to call and easier to understand. They will be simpler because they take fewer arguments. They will be easier to understand because they can be given more meaningful names.
A typical example is any function that takes a boolean as one of its arguments, indicating a caller-preference of some kind. Example:
function runTest (haltOnError, testCase)
{ /* Run the test and if 1st argument is true
and test-case fails, throw an error. If
1st arg is false just return a boolean
telling whether the test succeeded or not.
*/
...
}
Now if you call runTest() many times, sometimes with true, sometime with false, it might be a good idea to refactor it into two single-argument functions, by using Partial Application:
var runTestHalt = applyP (true, aTestCase);
var runTestNoHalt = applyP (false, aTestCase);
...
runTestHalt (myTestCase);
// runTestNoHalt (myTestCase);
The new partially applied functions are easier to understand because their names can express more about their semantics, because the semantics is simply simpler.There are fewer or no IF-THEN conditions you need to understand. If you instead use the original test-function, your code would look like this:
runTest (false, myTestCase) ;
Looking at the runTest() -call above it's not clear at all what is the meaning of the first argument. We can see it is false, but what does that MEAN? It's hard to remember, takes some effort to look it up, and easy to pass a wrong value to it. Maybe you changed its meaning, and forgot to change some places where it was already called. When the error hits the fan in production, start debugging.
The First Rule of O-O Programmers: Beware of IF-THEN !
In the accompanying code-file I provide an implementation of ClassCloud.applyP() which does partial-application for you. More than that, it returns functions that can successively partially apply themselves, to further arguments. Here's an excerpt from the unit-tests inside the code-file that show how it can be used:
function f2 (x,y){return x + y}
ok (applyP(f2, 1, 2) == 3);
ok (applyP(f2, 1 ) (2) == 3);
ok (applyP(f2 ) (1) (2) == 3);
ok (applyP(f2 ) (1,2) == 3);
The last two tests above are interesting because they show that we can do partial application by providing ZERO arguments to fix, even though the function really needs more to calculate its value. Why would we do partial application with zero fixed arguments? The last two lines give us the clue: applyP(f2) returns a function that can be called exactly like its argument-function f2:
ok ( applyP (f2) (1, 2) === f2 (1, 2) );
So it can do the same thing. But, it can also do MORE: It can apply ITS arguments partially, in separate calls as shown above and here again, still returning the same result as f2:
ok ( applyP (f2) (1)(2) === f2 (1, 2));
We could say that applyP(f2) is not just a different version of f2(), it is a BETTER version of it. It can do everything f2()does, and then some: It can take its arguments incrementally.
applyP() as shown by above examples does partial application. But depending on how many arguments you give it, there may be more partial application left you can do with its result. Hence the title of this blog-post: Partial Partial Application. The results of applyP() can take care of the further rounds of partial application themselves, and they can do that in multiple stages as well - partially. applyP() can do partial application partially, or fully.
So how is Partial Application related to currying? My previous blog-post was about currying. In that post I showed how a function for calculating max. of two numbers could be implemented in the "curried form" as two single-argument functions, one inside the other, the inner function returned as the result of the outer one:
function max(a)
{ return maxB;
function maxB (b)
{ return a < b ? b : a;
}
}
Note that the above function is not doing "currying". It is the result of you (or me) having done it. Making your compiler do it automatically is possible but a lot of work; it basically means implementing your own PL. Some PLs do it for you. Whereas creating a function like applyP() should be within the realm of the practical, as shown by the accompanying, rather small file of JavaScript.
If you look at that code you will see that applyP()is mostly implemented by applyPBasic()which contains AND RETURNS the inner function newFunk(). That is basically the curried form shown for max() above. Currying is the way to implement Partial Application!
There's one more thing to know about ClassCloud.applyP(): It relies on the number of declared arguments of the base-function. If you use it with functions that rely on varying number of arguments, your results might vary (pun intended!). If you call it with more than the declared number of arguments you get an error, which is useful for error-checking. It checks that your assumptions about how many arguments a function takes are correct.
You can copy the MIT-licensed implementation of applyP() including its tests from http://panuviljamaablog.blogspot.com/2014/07/classcloudapplypjs.html .
© 2014 Panu Viljamaa. All rights reserved
No comments:
Post a Comment