Saturday, October 6, 2012

Currying in JavaScript

Currying or partial function are very important concepts in functional programming. Most of the time these terms are used interchangeably, but few experts mention slight difference between them.
function returing object - Constructor
object returning object Lets try to understand the concept behind Curry and partial function.

Partial function-
Lets take a simple example of add method.

function add(a,b){
   return a+b;
}
alert(add(5,6)); //11
Now i want to modify the above function, so that i can execute it in the following manner -
add(5,6); //11
add(5)(6); //11
var a = add(5);
a(4); // 9
a(3); //8

If we execute add(5) in the above mentioned definition, then it will return NaN, as b is undefined.
Now if see the requirement properly add(5) should return a function which can take another parameter and finally return the result by adding the first value. lets try to write that function -


function add(a,b){
    if(typeof b == 'undefined'){
        return function(c){
            return a+c;
        };
    }
   return a+b;
}
var b = add(5);
alert(b(3)); //8
alert(add(3,4)); //7
If you see the above example, its returning another function if 2nd parameter is not passed. Moreover the inner function retains the value of passed parameter in the form of closure.

It will be very difficult to scale this functionality, because we will have to write this kind of logic for every function. Say in future another function(multiply) comes and we have to write similar functionality for that as well. So its always possible to create another function say createPartial which an act as a generic solution for this. Have a look-


function partial(fn) {
    var aps = Array.prototype.slice,
            args = aps.call( arguments, 1 );
    return function() {
        return fn.apply( this, args.concat(aps.call( arguments )) );
    };
}
function add( a, b ) {
    return a + b;
}
function multiply(a,b){
    return a*b;
}
var add1 = partial( add, 1 );
add1( 2 );
var multi2 = partial(multiply,3);
multi2(4);

In the above example we have created a reusable partial function, which is used to create a partial function for add as well as multiply.

Curry-
Currying can be described as transforming a function of N arguments in such a way that it can be called as a chain of N functions each with a single argument.
JavaScript does not provide inbuilt Curry function but its always possible to make our own.

function curry(/* n,*/ fn /*, args...*/) {
    var n,
            aps = Array.prototype.slice,
            orig_args = aps.call( arguments, 1 );

    if ( typeof fn === 'number' ) {
        n = fn;
        fn = orig_args.shift();
    } else {
        n = fn.length;
    }
    return function() {
        var args = orig_args.concat( aps.call( arguments ) );

        return args.length < n
                ? curry.apply( this, [ n, fn ].concat( args ) )
                : fn.apply( this, args );
    };
}
var i = 0;
function a( x, y, z ) {
    console.log( ++i + ': ' + x + ' and ' + y + ' or ' + z );
};
a( 'x', 'y', 'z' );     // "1: x and y or z"
var b = curry( a );
b();                    // nothing logged, `a` not invoked ------------(1)
b( 'x' );               // nothing logged, `a` not invoked------------(2)
b( 'x' )( 'y' )( 'z' ); // "2: x and y or z" --------------------------(3)
b( 'x', 'y', 'z' );     // "3: x and y or z"  --------------------------(4)



Lets try to understand the above code. Once we call var b = curry(a); the following function get assigned to b-

 return function() {
        var args = orig_args.concat( aps.call( arguments ) );
        return args.length < n
                ? curry.apply( this, [ n, fn ].concat( args ) )
                : fn.apply( this, args );
    };
If you see here we have passed orig_args in the form of closure, which means the originally passed arguments are intact. In this function we check if the arguments are sufficient or not. In our case the n(expected number of arguments) is 3. If it is sufficient then the function will execute with the parameter passed. If we have not got expected number of inputs the function returns another function along with the parameter passed till now.
In the Above code we have used curry function again and passed n,fn and other arguments. If you see in the Curry function we have checked if fn==number, this is to check if the Curry function is been called in following manner-
curry( expected number of args , function definition , other arguments)
If the format is so, then we treat the first argument as the number of args expected, and use the shift property to get the function definition.
Moreover If you see we have called slice method of Array on argument object that is because arguments is array-like, it is not an array. This means that while it has a .length property and numeric indices, it doesn't have the normal Array.concat or .slice methods. In order to convert the arguments object into an array, the native Array.slice method (Array.prototype.slice) is invoked as if it existed on the arguments object via call.
lets try to see the usecases -
1-b();-> Here is the flow what happens -

1) as number of required args is 3 and we didnt pass any args this call to curry.apply( this, [ n, fn ].concat(args))
2) Call Curry function with arguments as 3 and function definition
If you see the if block fn == 'number' is true , thus n = 3 will be assigned and the function definition is assigned to fn using shift method.If you see orig_args will be empty as there is no arguments left.
With this state the function is returned -
 return function() {
        var args = orig_args.concat( aps.call(arguments) );
        return args.length < n
                ? curry.apply( this, [ n, fn ].concat( args ) )
                : fn.apply( this, args );
    };

There are 4 closures - orig_args, aps,fn and n.

2. b('x')-> The flow will be identical to what is mentioned above except this time orig_args will contain x as one of the parameter passed and this value will be passed to the function in the form of closure.

3. b('x')('y')('z') -> b('x') retuns a function which we mentioned above and in this case that function get execute with input parameter as 'y', which again returns another function(which has values x and y in its orig_args) and then finally once we execute it with input as z, it has all the required parameter in it, and hence give the expected output.

4. n('x','y','z') -> this is very straight forward and returns the output as expected.

Partial function Vs Curry -
Most of the times these terms are used interchangeably, but few experts consider them different in the following way -
Unlike partial application, which invokes the partially applied function whether all of its arguments have been satisfied or not, a curried function will only be invoked once all of its arguments have been satisfied, otherwise a function will be returned.
Example-
var a1 = partial(add,1);
var a2 = curry(add,1);
a1(); --> NaN
a2() --> function

Ref - http://msdn.microsoft.com/en-us/magazine/gg575560.aspx


No comments:

Post a Comment