Friday, February 21, 2014

A few cool Javascript functions

For me,learning Javascript has been an extra-curricular activity,my work has never required me to use JavaScript at any time.In fact,as an app developer working on native Android apps,I am almost completely sealed off from it.So,I have never been obliged to learn to write code to handle DOM events or use jQuery and some of it's plugins to do cool things.

This left me free to just relax,unwind and experience new levels of puzzlement(http://www.merriam-webster.com/dictionary/puzzlement),bewilderment,embarrasment and frustration while exploring the language using blogs,books and the occasional video as my resources.Although it seems simple to access,the language had hidden layers giving the programmer a billion ways to shoot himself in the foot.And just like Android's million Runtime errors it appealed to me.And finally,the idea that a function isnt set in stone,that you could modify how it is called,who calls it and even what it is called on using other functions.

These are some interesting functions I have learnt about:

1.foreach,map and mapWith:


1:  var foreach=function(arr,fn)  
2:  {  
3:            var i;  
4:            for(i=0;i<arr.length;i++)  
5:            {  
6:                 fn(arr[i]);  
7:            }  
8:  };  
9:  var map=function(fn,arr)  
10:  {  
11:       var res=[];  
12:       foreach(arr,function(elem){  
13:            res.push(fn(elem));  
14:       });       
15:       return res;  
16:  };  
17:  var mapwith=function(fn)  
18:  {  
19:       return function(arr)  
20:       {  
21:            return map(fn,arr);  
22:       }  
23:  };  

The first two functions I encountered in Eloquent Javascript,the third I found in Javascript Allonge.This is the kind of tool that I dreamed of possessing when I first started programming,the ability to take an arbitrarily large array,process it and return the results as an array.I try and use these functions as much as possible.In fact,whenever I see an array in Javascript,I cant help but think of map and mapWith.

I have come to understand that Array.prototype now has a map method which is much simpler to use because it is called like this:


 var numbers = [1, 4, 9];  
 var roots = numbers.map(Math.sqrt);  

2.partial application and flip

Once upon a time,I saw a freaking crazy video featuring libraries and code I had no clue about but the idea was so cool that I decided to learn enough JavaScript to understand how to do it.The video was a talk about how underscore js' partial application,autocurry and certain other functions were not to the liking of the speaker Brian Lonsdorf's liking.This was another tool I wanted when I was learning to program,so it was another dream come true for me.



1:  var autoCurry=function(fn)  
2:  {  
3:       var n=fn.length;  
4:       return function()  
5:       {  
6:            var args=[];  
7:            var _slice=Array.prototype.slice;  
8:            function argsCollector()  
9:            {  
10:                 //if this function were not a named function,it's name would have to be looked up in the enclosing environment   
11:                 //instead of within itself...  
12:                 args=args.concat(_slice.apply(arguments));  
13:                 if(args.length>=n)  
14:                 {  
15:                      return fn.apply(null,args);  
16:                 }  
17:                 return argsCollector;  
18:            }  
19:            return argsCollector.apply(null,_slice.apply(arguments));  
20:       }  
21:  };  

I found this function in a gist on Github(dont exactly remember where).I also understand that the arguments Array-like-object inherits from Array.prototype since ES5 and therfore can have Array method calls performed on it.

Then there is my version,I got stuck on this for a long time because I was not updating the Array after concatenation.



1:  var myCurry=function(fn)  
2:  {  
3:       var len=fn.length;  
4:       var _slice=Array.prototype.slice;  
5:       var args=_slice.call(arguments,1);  
6:       console.log(args.length);  
7:       return function next()  
8:       {  
9:            var rem=_slice.call(arguments,0)  
10:            //args.concat(rem);//concat returns a new Array and returns another Array...  
11:            //Array.prototype.push.call(args,rem);  
12:            args=args.concat(rem);  
13:            console.log(args.length);  
14:            if(len<=args.length)  
15:                 return fn.apply(null,args);  
16:            else  
17:                 return next;  
18:       }  
19:  }  

There are versions of this function in underscorejs and wujs libraries as well,I took a look at the wu js  implementation while reading Angus Croll's blog and it really confused me but they have an interesting idea of calling it with a length parameter which means you can use


 var len=length || fn.length;  


There is also a version of this function written by John  Resig in the book Secrets of the JavaScript Ninja that someone posted on stackoverflow:



1:  Function.prototype.partial = function() {  
2:     var fn = this, args = Array.prototype.slice.call(arguments);  
3:     return function() {  
4:      var arg = 0;  
5:      for (var i = 0; i < args.length && arg < arguments.length; i++) {  
6:       if (args[i] === undefined) {  
7:        args[i] = arguments[arg++]; //This line is where the confusion is  
8:       }  
9:      }  
10:      return fn.apply(this, args);  
11:     };  
12:   };  

This function is called on another function either as a method or using call or apply,the function on which this has been called becomes this and is stored in fn.The interesting part is that if any of the arguments supplied to partial is undefined,it gets replaced by an argument supplied to the function.On the other hand it does not check for function's length.

I also loved the flip function which allows you to flip the arguments of a function,this is another function that I always wanted to do,just because it was cool,not much for practical utility of it.I found an implementation in the excellent Javascript Allonge:


1:  var flip=function(fn)  
2:  {  
3:    return function(first)  
4:    {  
5:      return function(second)  
6:      {  
7:        fn.call(null,second,first);  
8:      }  
9:    }  
10:  };  

This function uses only two parameters,to extend it to have more than two parameters .This is a function I created so that I could flip a function with multiple arguments conveniantly.I decided not to curry till all the arguments of a function are obtained,instead I decided to append undefined to the end of the array representing arguments in case there were fewer arguments than required.But before I could do this I flipped the array representing the arguments so that the function could theoretically run with enough parameters.I am still agonizing over slicing the array to the length specified.



1:  var flip2=function(fn,length)  
2:  {  
3:    var len=length || fn.length;  
4:    var args=Array.prototype.slice.call(arguments,1);  
5:    return function()  
6:    {  
7:     args=args.concat(Array.prototype.slice.call(arguments,0));  
8:     var missingArgs,argPadding;  
9:     if(args.length<=len)  
10:     {  
11:      missingArgs=Math.max(0,len-args.length-1);  
12:      argPadding=new Array(missingArgs);  
13:      args.reverse();  
14:      args=args.concat(argPadding);  
15:     }  
16:     else  
17:      args.reverse();  
18:     fn.apply(null,args);  
19:    }  
20:  };  

This function was also influenced by an implementation of partial application in the first recipes section of Javascript Allonge.

3.serial

I was watching a Node presentation given  by Nick Nisi  at the Nebraska Javascript Group on Youtube,amon other things he was also talking about an npm library called comb which has a function called serial which will take an array of functions as it's input and return(I'm guessing) an array of values(some of which could be functions),I decided to take a stab at writing it.Here is a rather simplistic implementation of serial which I managed to complete before I was again inuandated with work.


1:  var serial = function serial(funcs, params) {  
2:   //it does not execute all the functions unless you have specified enough or more than enough parameters.   
3:   //it would be nice to return all the values as an array.  
4:   //wont work if any of the functions use arguments in their call(ofcourse),so this cannot be used with a lot of stuff  
5:   var i, args, key,  
6:   max = Math.min(funcs.length, params.length),  
7:    res = [];  
8:   for (i = 0; i < max; i++) {  
9:    args = [];  
10:    if (params[i] != null) {  
11:     for (key in params[i]) {  
12:      if (params[i].hasOwnProperty(key)) {  
13:       //would be nice to map values here and parameters in the function...  
14:       console.log(params[i]['' + key]);  
15:       args.push(params[i]['' + key]);  
16:      }  
17:     }  
18:    }  
19:    res.push(funcs[i].apply(null, args));  
20:    args = [];  
21:   }  
22:   return res;  
23:  };  

As specifed in the comments along with the code this function only executes a function if an object consisting of parameters is provided for it.If a function does not have enough arguments the result is not obtained.

I guess we could return the autocurry function in order to ensure that the remaining arguments are passed to the function before it is executed but that would mean that the existing argument set is discarded and not applied to the function,which then has no option but to start anew.The function will return undefined in case it depends entirely upon the arguments array-like-object for it's execution.




1:  serial([function()  
2:      {  
3:        return 'Hello world'  
4:      },  
5:      function(x,y)  
6:      {  
7:        return x+y;  
8:      },  
9:      function(x,y,z)  
10:      {  
11:        return x+y+z;  
12:      },  
13:      function()  
14:      {  
15:        var res;  
16:        var args=Array.prototype.slice(arguments,null);  
17:        for(var i=0;i<args.length;i++)  
18:        {  
19:          res+=args[i];  
20:        }  
21:        return res;  
22:      }],  
23:      [null,  
24:       {x:2,y:3},  
25:       {x:2,y:4},  
26:       {x:2,y:3,z:5}  
27:       ]);  

I would also like to map the arguments to the function as they were passed.Now,as you might already have guessed the arguments are passed based on a first-come first-serve,I do not care about your name way but it would just be nice(atleast in my opinion) if we could do mapping of arguments to the function by name.


For that I have discovered this function(thank you Stack Overflow) whose input is a function which it converts to a string and uses a regular expression to isolate the values between ( and ) and splits them based on the , charecter.


1:  var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;  
2:  var getParamNames=function getParamNames(func) {  
3:   var fnStr = func.toString().replace(STRIP_COMMENTS, '')  
4:   var result = fnStr.slice(fnStr.indexOf('(') + 1, fnStr.indexOf(')')).match(/([^\s,]+)/g)  
5:   if (result === null) result = []  
6:   return result  
7:  };  


1:  var serial=function serial(funcs,params)  
2:  {  
3:   var i,args,key,  
4:   max=Math.min(funcs.length,params.length),res=[];  
5:   for(i=0;i<max;i++)  
6:   {  
7:     args=[];  
8:     if(params[i]!=null)  
9:     {  
10:      for(key in params[i])  
11:     {  
12:       if(params[i].hasOwnProperty(key))  
13:       {  
14:         //would be nice to map values here and parameters in the function...  
15:         console.log(params[i][''+key]);  
16:         args.push(params[i][''+key]);  
17:       }  
18:     }  
19:     }  
20:     if(funcs[i].length>args.length)  
21:     {  
22:      //this will start applying the function anew with new arguments  
23:      res.push(mycurry(funcs[i]));  
24:     }  
25:     else  
26:     {  
27:      res.push(funcs[i].apply(null,args));   
28:     }  
29:      args=[];   
30:   }  
31:   return res;  
32:  };  

The only issue I percieve with this function  is that it requires that the arguments be passed anew.Any previous arguments that we might have passed to it before are sent to a deep dark abyss.You could unwind the args arrays and pass it to the autocurry function or you could instead do this horrifyingly tortuously(http://www.thefreedictionary.com/tortuously) which is different from  torturous(lesson learned!!!) chained thing.But hey,it works.

  res.push(mycurry.apply(null,[funcs[i]].concat(args)));  

Some of the functions I represented here might not be the most used or the most useful functions but they are fun ideas and dreams turned into reality.I wanted to write about so many other functions but these were the functions that came to mind when I conceived of this blog post.

Update:

Here is a function that maps the arguments of a function to the properties of an object.If a property matching the parameter name cannot be found,it instead places undefined there.Here is the function:


1:  var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;  
2:  var getParamNames=function getParamNames(func) {  
3:   var fnStr = func.toString().replace(STRIP_COMMENTS, '')  
4:   var result = fnStr.slice(fnStr.indexOf('(') + 1, fnStr.indexOf(')')).match(/([^\s,]+)/g)  
5:   if (result === null) result = []  
6:   return result  
7:  };  
8:  //pick properties from an object based on a function's arguments  
9:  var chooseArguments=function chooseArguments(fn)  
10:  {  
11:   var fn_args=getParamNames(fn);  
12:   return function(obj)  
13:   {  
14:    var i,res=[],key;  
15:    for(i=0;i<fn_args.length;i++)  
16:    {  
17:     if(obj[fn_args[i]]!=null)  
18:     {  
19:      res.push(obj[fn_args[i]]);  
20:     }  
21:      else  
22:        res.push(void 0);  
23:    }  
24:     return res;  
25:   }  
26:  }  
27:  var checkAdd=chooseArguments(function(x,y){return x+y});  
28:  var args_add=checkAdd({z:10,y:3});  
29:  alert(args_add);  

This function does not play nicely with the currying function as we always have enough arguments,although some of these arguments are undefined.So,we need a new currying function that can only replaces undefined values in your array of arguments,an initial version looks like this:


1:  var replaceUndefinedArgs=function(arr)  
2:  {  
3:    var i,j=0;  
4:    var args=Array.prototype.slice.call(arguments,1);  
5:    for(i=0;i<arr.length;i++)  
6:    {  
7:      if(arr[i]==null)  
8:      {  
9:        if(j<args.length)  
10:        {  
11:          arr[i]=args[j];  
12:          j++;  
13:        }  
14:      }    
15:    }  
16:  //arr is copied by reference so a mutation to arr ripples through space and time...so no need //to return arr  
17:  };    

I am an unfortunate soul because the word what-if scares me and drives me to do some crazy stuf.So here it is,WHAT IF you did not pass enough arguments to dispel undefined from the array.You must find out how many arguments you need to curry for in the initial array. Ensure that the function keeps returning another function until the exact number of arguments has been reached.

This is a function that keeps querying for more arguments until it has enough arguments to replace all the undefined values in your array,this is a version of the auto-currying function that just uses an array instead of a function,pretty straightforward by now,I guess.We dont return the array,of course because it has copied by reference and need not be returned.

1:  var replaceUndefinedArgs=function(arr)  
2:  {  
3:    var i,len=0;  
4:    for(i=0;i<arr.length;i++)  
5:    {  
6:      if(arr[i]==null)  
7:        len++;  
8:    }    
9:    var args=Array.prototype.slice.call(arguments,1);  
10:    return function next()  
11:    {  
12:      var j=0;  
13:      args=args.concat(Array.prototype.slice.call(arguments,0));  
14:      if(len>0 && args.length<len)  
15:      {  
16:       return next;  
17:      }  
18:      else  
19:      {  
20:       for(i=0;i<arr.length;i++)  
21:       {  
22:         if(arr[i]==null)  
23:         {  
24:          arr[i]=args[j];  
25:          j++;  
26:         }    
27:       }  
28:      }    
29:    }     
30:  };  

So,I guess this is kind of a generic pattern in functional programming to do,return a function,task it with doing something.This function takes care of odd-user behaviour and other environmental factors.When the function has exactly what you need,you can do what you originally intented to to.

Now these functions are ready to be added to serial,so that we can map arguments to values,setting undefined when no value is found,if nessecary we can force these undefined values to have values by currying for them.

No comments:

Post a Comment