giwook
giwook

Reputation: 580

How do I use the logical NOT (!) operator when invoking functions in JS?

I'm re-creating functions from the underscore library but I'm running into a roadblock while trying to implement the _.reject() function. For the purposes of this question, I'll include the code I've written for three functions: _.each(), _.filter(), and _.reject().

_.each = function(collection, iterator) {
  if (Array.isArray(collection)) {
    for (var i = 0; i < collection.length; i++) {
      iterator(collection[i], i, collection);
    }
  } else {
    for (var i in collection) {
      iterator(collection[i], i, collection);
    }
  }
};

_.filter = function(collection, test) {
    var results = [];

    _.each(collection, function(i) {
      if (test(i)) {
        results.push(i);
      }
    })

    return results;
};

And here's the code for the function that I'm getting a problem with, the _.reject() method, along with the isEven() function that I'm passing in as the test argument.

_.reject = function(collection, test) {
    return _.filter(collection, !test);
}; 

var isEven = function(x) {
    if (x % 2 === 0) return true;
    return false;
};

According to MDN's page on Expressions and Operators, the Logical NOT (!) operator Returns false if its single operand can be converted to true; otherwise, returns true.

But when I run the following code _.reject([1,2,3], isEven) I get an error saying that test is not a function. Why am I unable to use the ! operator while invoking a function (e.g., _.filter([1,2,3], !isEven))?

Upvotes: 3

Views: 2068

Answers (3)

CollinD
CollinD

Reputation: 7573

You cannot negate a function. It's a meaningless thing to do (unless you really want the value false without typing it). Instead you want to negate the function's return value. I suspect what you want is something like so

_.reject = function(collection, test) {
  return _.filter(collection, function(e) { return !test(e); });
}

Upvotes: 3

Felix Kling
Felix Kling

Reputation: 816364

Why am I unable to use the ! operator while invoking a function (e.g., _.filter([1,2,3], !isEven))?

Note that you are actually not invoking isEven, you are merely referencing it. As you said, ! "Returns false if its single operand can be converted to true; otherwise, returns true."

isEven is a reference to a function, i.e. an object. Objects convert to true, hence !test results in false:

_.filter([1,2,3], false))

Now you are passing a Boolean instead of a function to _.filter, hence the error message "test is not a function".

Instead, you have to pass a function that negates the result of the test function:

_.filter([1,2,3], function() {
    return !test.apply(this, arguments);
});

Upvotes: 7

T.J. Crowder
T.J. Crowder

Reputation: 1074158

When you refer to a function, rather than calling it, you're referring to the function's object reference:

function foo() {
    alert("Hi there");
}

var f = foo; // <== Getting the function's reference, not calling it
f();         // <== Now we call it

So !isEven would be negating the function reference. Since isEven is a non-null reference, it's truthy; and so !isEven is false. Not what you want. :-)

Your reject could be written with a function that calls test and inverts its return value:

_.reject = function(collection, test) {
  return _.filter(collection, function(e) { return !test(e); });
};

Or if you want to go the functional programming approach, you can write a function that, when called, will return a new function that negates the return value:

function not(f) {
    return function() {
        return !f.apply(this, arguments);
    };
}

Then anywhere you want to invert a callback's meaning, you'd just use invert:

_.reject = function(collection, test) {
  return _.filter(collection, not(test));
};

Upvotes: 7

Related Questions