System.Data
System.Data

Reputation: 3968

How to simplify this filter written in underscore.js?

Given is an object with a bunch of conditions:

var conditions = {
    even: function (i) { return i % 2 == 0; },

    greatherThan: function (i) { return i > 10; },

    inValidRange: function (i) {
        return i > 20 && i < 100;
    }
};

and an array numbers in a range starting from 0 up to 39: var numbers = _.range(0, 40);.

I want to filter numbers by every condition. I used underscore.js to do this:

var result = _.filter(numbers, function(current) {

    return _.all(_.values(conditions), function(f) { 
        return f(current);
    });

});
// returns [ 22, 24, 26, 28, 30, 32, 34, 36, 38 ]

It works fine, but unfortunately, the code above looks weird and it is pretty confusing.

How do I simplify this code in order to make it more readable and understandable?

Upvotes: 3

Views: 377

Answers (2)

Bergi
Bergi

Reputation: 664970

This helper function might be useful:

_.mixin({
    invokeWith: function() {
        var args = arguments;
        return function(fn) {
             return fn.apply(null, args);
        };
    }
});

It also would be better if your conditions was an array right away. You can either use an array of named functions:

var conditions = [
    function even(i) { return i % 2 == 0; },
    function greaterThan(i) { return i > 10; },
    function inValidRange(i) { return i > settings.validRange.from && i < settings.validRange.to; }
];

or transform it before, so that we can omit (and don't need to repeatedly call) the values function:

conditions = _.values(conditions);

Now you can shorten your code to

var result = _.filter(numbers, function(current) {
    return _.all(conditions, _.invokeWith(current));
});

It might be more complicated to understand what happens because most people would have to look up that invokeWith function, but the concept is easier to grasp since the code is quite declarative, containing all verbs to build the natural expression invoke all conditions with current.

If one wants to remove the outer lambda expression as well, it would get a little shorter but harder to understand:

// functional programming FTW :-)
var result = _.filter(numbers, _.compose(_.partial(_.all, conditions), _.invokeWith));

Upvotes: 4

Paul
Paul

Reputation: 141877

There is no need to rewrite that code; put it in a named function:

function filterByConditions(values, conditions){
  return _.filter(values, function(current) {

    return _.all(_.values(conditions), function(f) { 
      return f(current);
    });

  });
}

The code within the function is not that complex, and shouldn't confuse anyone familiar with underscore.js. If you deem it necessary than you can add comments to the body of the function. Either way, when you use the function elsewhere it should be clear what the function does from the name:

var result = filterByConditions(numbers, conditions);

If you want to simplify the body of the function, so that developers who are not familiar with underscore.js can still read it you can replace _all and _values with a simple for ... in loop. Most Javascript developers can infer what _.filter does without ever having used underscore.js:

function filterByConditions(values, conditions){
  return _.filter(values, function(current){

    for(condition_name in conditions){

      if(!conditions[condition_name](current)){
        // Condition failed
        return false;
      }

    }

    // All conditions passed
    return true;

  });
}

Upvotes: 0

Related Questions