Sunday, 12 October 2014

Canceling "Functional Style" Iteration

Somehow the other day I ended up in this StackOverflow question about how to get out (break) from an Array.prototype.forEach "functional loop". I gave it some thought before reading the answers, and all I could think of was throwing an specific Exception that you could later on catch and swallow. All this brought to my mind memories of those times when the venerable prototype.js was all the rage, as I thought to remember that they were using something similar for the each method that they were mixing into Array.prototype.

To my surprise, when looking into the current documentation, I could not find any mention about it. However, you'll find some old discussions referring to that solution:

So hopefully I was not hallucinating. This led me to think why at some point they decided to remove that feature from prototype.js. Well, if we think of each/forEach as the equivalent of the classic for (..;..;..) I think it makes sense. The most common (correct I would say) use of the classic for (..;..;..) loop is to traverse a whole collection (i.e. the stop condition is having reached the end of the collection). If you are going to traverse a collection until other certain condition is met, the normal, semantic way to do it is by means of a While loop. So if you need to break from a for loop, in most cases I think you should switch to a while loop.

This said, it occurred to me that probably we should have some sort of Array.prototype.while method, something like this:

//function stopCondition(item, index, array) returns true to indicate stop
//function action(item, index, array)
Array.prototype.while = function(stopCondition, action){
 var curPos = 0;
 while (curPos < this.length && !stopCondition(this[curPos], curPos, this)){
  action(this[curPos], curPos, this);
  curPos++;
 }
};

var items = ["Asturies", "Armenia", "Austria", "France", "Germany"];

items.while(
 function(it){
  return it[0] != "A";
 }, 
 function(it){
  console.log(it.toUpperCase());
 }
);

I think it's obviously much better that using forEach and a "break" exception that we'll later on ignore:

try{
 items.forEach(function(it){
  if(it[0] != "A"){
   throw "break";
  }
  console.log(it.toUpperCase());
 });
}
catch (ex){
 if(ex != "break"){
  throw ex;
 }
}

Well, actually rolling out your own prototype.while function is unnecessary. In the end what we are doing is selecting elements until a condition is met and applying a function to them, so we already can achieve the same effect by leveraging some of the functionality provided by for example lodash.js: first for doing the selection and then a normal forEach.

var _ = require('./libs/lodash.js');
_.first(items, function(it){
 return it[0] == "A";
})
.forEach(function(it){
 console.log(it.toUpperCase());
});

No comments:

Post a Comment