5 April 2013

Curried functions and partial application in javascript

Currying

In my previous post I mentioned I was letting a javascript function stay uncurried because that is how most languages define function calling.

So what is function currying?
Wikipedia explains it nicely ( http://en.wikipedia.org/wiki/Currying ). It is making the function have the ability to receive its arguments one at the time. Also, if not all arguments are supplied you are returned a function that receives all the remaining arguments one at the time, another curried function.

For example consider the call:
myfun(a, b, c, d)

a curried version of this would be
myfun(a)(b)(c)(d)

It became a one-argument functions chaining, like I try to show possibly in a clearer manner by adding some unnecessary extra parenthesis and arguably pretty colours
( ( ( myfun(a) )(b) )(c) )(d)

So if we do
myfun(a)(b) it returns a function that takes (c) and returns another function that takes (d) to return the final value. When you just curry an uncurried function it is most likely okay if computations are just delayed until all arguments are available. Otherwise, in a curried from scratch version, one may want to advance some work if it's doable and reasonable enough.


Any partial application

We don't have to go in one-argument functions steps. If you provide more than one argument to the function but not all, it can in turn give you another partial-able function but capable of receiving all the remaining arguments at once.

So having the same:
myfun(a, b, c, d)

enables us to call it like
myfun(a, b)(c, d)
or any other combination we need.

And the following
myfun(a, b)
would return a function that also can optionally receive (c, d) at once to return the final value. Similarly one might want to consider doing something with the thus far available values to save time, or maybe do some side-effect.

To take any function in javascript and make it able to receive its arguments in this later fashion I will code a new function mkpar that will make any function partial-able just by returning a wrapper that does the argument holding.


//To be able to become partial a function needs to know its meant arity all the way
//A specific arity may be enforced with opt_ari, otherwise fn.length will be used
function mkpar(fn, opt_ari, opt_ctx){
    var wraper = 
        function (){
            var arity = opt_ari === undefined ? fn.length : opt_ari;
            if(arguments.length === arity){
                return fn.apply(opt_ctx === undefined ? this : opt_ctx, arguments);
            }else{
                var ini_args = Array.prototype.slice.call(arguments, 0);//get a normal array
                return (
                    function () {
                        var these_args = Array.prototype.slice.call(arguments, 0);
                        return wraper.apply(this, ini_args.concat(these_args)); 
                    }
                );
            }
        };
    return wraper;
}

Some tests, press result to see them:

Now if I were to do a curried function from scratch that had incorporated some mkpar structure into it I could do some partial computations in the else block and save that state there.

What about currying?
A curried function is supposed to take its arguments one by one. Something like my next snippet does to it


function curry(fn, opt_ari, opt_ctx){
    var arg_acc = [];
    var arity = opt_ari === undefined ? fn.length : opt_ari;
    var wraper = 
        function (){
            if(arguments.length > 1){
                throw new Error("curried function called with more than one argument");
            }else{
                if(arguments.length === 1){
                    arg_acc.push(arguments[0]);
                }else{ //remove else if wanting a call () to return same state
                    throw new Error("curried function called with no arguments");
                }
                if(arg_acc.length === arity){
                    return fn.apply(opt_ctx === undefined ? this : opt_ctx, arg_acc);
                }else{
                    return (
                        function () {
                            var these_args = Array.prototype.slice.call(arguments, 0);
                            return wraper.apply(this, these_args); 
                        }
                    );
                }
            }
        };
    return wraper;
}

Some tests, press result to see them:


No comments:

Post a Comment