Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

add _.complement #1503

Closed
wants to merge 2 commits into from
@davidchambers
  • Should _.complement take an optional context argument? Currently it does not; I followed underscore-contrib's lead here.

  • It's quite hard to come up with a straightforward example for the documentation which isn't better expressed with one of Underscore's existing complements (we can't use _.filter, for example, given the existence of _.reject). Interestingly, had Underscore included _.complement from day one, we mightn't need _.reject and friends.

  • Should redefining _.reject, _.omit, etc. in terms of _.complement be part of this pull request?

underscore.js
@@ -772,6 +772,14 @@
return _.partial(wrapper, func);
};
+ // Returns a function that takes the same arguments as the given predicate,
+ // has the same effects, if any, and returns the opposite truth value.
+ _.complement = function(predicate) {
+ return function() {
+ return !predicate.apply(null, arguments);
@braddunbar Collaborator

Shouldn't we use whatever context is given?

return !predicate.apply(this, arguments);

Yup :+1:

Updated. Suggestions as to a good test case for this would be appreciated. :)

Here's an example that uses context

var Person = function (gender) { this.gender = gender; };
var isMale = function () { return this.gender === 'male'; };
Person.prototype.isMale = isMale;
Person.prototype.isFemale = _.complement(isMale);
var larry = new Person('male');
ok(larry.isMale());
ok(!larry.isFemale());

Thanks, @caseywebdev! Aside from treating gender as binary, this is a really good example. I'll try to think of a truly binary alternative.

@michaelficarra Collaborator

I cannot love you more, @davidchambers.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@jashkenas
Owner

Should redefining _.reject, _.omit, etc. in terms of _.complement be part of this pull request?

Yes. (Along with a JSPerf, please.)

@jashkenas
Owner

Lovely, but too wordy:

Returns a function that takes the same arguments as predicate,
has the same effects, if any, and returns the opposite truth value.

Let's try this instead:

Returns a negated version of predicate.

@michaelficarra
Collaborator

But "negated version" could mean so many things, no? Explicit reads better to me.

@davidchambers

For the record, I lifted the description from clojure.core/complement:

Usage: (complement f)

Takes a fn f and returns a fn that takes the same arguments as f, has the same effects, if any, and returns the opposite truth value.

This makes it clear that the function returned has the same side effects (hopefully none!) as the function provided. Perhaps it's okay to omit this, though, as it's understood that predicates should not have side effects.

@jdalton

Is it a bad sign that from the name of the method I can't begin to narrow down what the function does? Is there a way to describe the method that incorporates the theme/vibe of the name?

@jashkenas
Owner

Is it a bad sign that from the name of the method I can't begin to narrow down what the function does? Is there a way to describe the method that incorporates the name?

Yes, it is. _.not would be the better choice for the name. Or _.negate.

@joshuacc

_.negate sounds fine to me. But _.not is easily confused with the ! operator. And as mentioned before, underscore-contrib already has a _.not which simply wraps !.

@jridgewell
Collaborator

Coming from a set-theory background, _.complement is perfect.

i.e.

var a = [1, 2, 3]
var isOdd = function(x) { return x % 2 == 1; }
var odds = _.filter(a, isOdd)
var evens = _.filter(a, _.complement(isOdd));
// evens =  a \ odds
@mehdishojaei

_.not is more straightforward.

@michaelficarra
Collaborator

@jdalton: If you think of f as testing membership of the input in a potentially infinite set S of inputs that cause f to return true, _.complement(f) tests membership of the input in SC, the complement of S.

@mehdishojaei

In the math world complement operates on sets and we can not consider functions as sets. They help _.filter, _.map and their friends to create sets.

@joshuacc

@fogus Any thoughts on the naming here?

@megawac
Collaborator

+1 for negate

@caseywebdev

_.not = _.negate = _.complement = :trollface:

@jdalton

In the math world complement operates on sets and we can not consider functions as sets. They help _.filter, _.map and their friends to create sets.

This! I was just going to say that in @jridgewell's example the compliment of odds [1, 3] is [2] but that's a stretch to project that name to the callback.

@fogus

_.complement is what we call it in contrib land

@jdalton

Naming is hard. Maybe _.converse. It implies "opposite" instead of "canceling-out" as _.negate.

@michaelficarra
Collaborator

@mehdishojaei: @jdalton: Pure functions are just lookup tables. They map their input values to an output value. In the case of predicates, they determine set inclusion. My vote is for _.complement.

@jdalton

I still don't think that's descriptive as the function can be used on its own outside the scope of a set or collection.

@jashkenas jashkenas closed this in d7b0c1a
@davidchambers davidchambers deleted the branch
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
Showing with 28 additions and 3 deletions.
  1. +13 −0 index.html
  2. +6 −0 test/functions.js
  3. +9 −3 underscore.js
View
13 index.html
@@ -262,6 +262,7 @@
<li>- <a href="#once">once</a></li>
<li>- <a href="#after">after</a></li>
<li>- <a href="#wrap">wrap</a></li>
+ <li>- <a href="#complement">complement</a></li>
<li>- <a href="#compose">compose</a></li>
</ul>
@@ -1222,6 +1223,18 @@ <h2 id="functions">Function (uh, ahem) Functions</h2>
=&gt; 'before, hello: moe, after'
</pre>
+ <p id="complement">
+ <b class="header">complement</b><code>_.complement(predicate)</code>
+ <br />
+ Returns a function that takes the same arguments as <b>predicate</b>,
+ has the same effects, if any, and returns the opposite truth value.
+ </p>
+ <pre>
+var isFalsy = _.complement(Boolean);
+_.find([-2, -1, 0, 1, 2], isFalsy);
+=&gt; 0
+</pre>
+
<p id="compose">
<b class="header">compose</b><code>_.compose(*functions)</code>
<br />
View
6 test/functions.js
@@ -388,6 +388,12 @@
deepEqual(ret, [noop, ['whats', 'your'], 'vector', 'victor']);
});
+ test('complement', function() {
+ var isOdd = function(n){ return (n & 1) == 1; };
+ equal(_.complement(isOdd)(2), true, 'should return the complement of the given function');
+ equal(_.complement(isOdd)(3), false, 'should return the complement of the given function');
+ });
+
test('compose', function() {
var greet = function(name){ return 'hi: ' + name; };
var exclaim = function(sentence){ return sentence + '!'; };
View
12 underscore.js
@@ -179,9 +179,7 @@
// Return all the elements for which a truth test fails.
_.reject = function(obj, predicate, context) {
- return _.filter(obj, function(value, index, list) {
- return !predicate.call(context, value, index, list);
- }, context);
+ return _.filter(obj, _.complement(predicate), context);
};
// Determine whether all of the elements match a truth test.
@@ -772,6 +770,14 @@
return _.partial(wrapper, func);
};
+ // Returns a function that takes the same arguments as the given predicate,
+ // has the same effects, if any, and returns the opposite truth value.
+ _.complement = function(predicate) {
+ return function() {
+ return !predicate.apply(this, arguments);
+ };
+ };
+
// Returns a function that is the composition of a list of functions, each
// consuming the return value of the function that follows.
_.compose = function() {
Something went wrong with that request. Please try again.