Should we add `Iterator::none`?

I'm used to having Enumerable#none? from Ruby and so I was a bit surprised when I noticed that Iterator::none isn't defined.

It is pretty easy to add via an extension trait but I thinking if it makes sense to be in std::iter::Iterator?

pub trait NoneIteratorExtension: Iterator {
    fn none<F>(self, predicate: F) -> bool
    where
        F: Fn(Self::Item) -> bool;
}

impl<T> NoneIteratorExtension for T
where
    T: Iterator,
{
    fn none<F>(mut self, predicate: F) -> bool
    where
        F: Fn(Self::Item) -> bool,
    {
        self.all(|item| !predicate(item))
    }
}
1 Like

This would be the same as !Iterator::any, which I find to be as clear as Iterator::none

7 Likes

True but personally don’t always think !any is clearer. And sometimes you have a long chain of iterator methods so adding ! way at the start is confusing.

1 Like

Instead of adding ! at the start of a long chain, you can use the Not trait and add .not() after .any(f).

1 Like

I personally think that any(f).not() is sufficient.

Also, in terms of pure vocabulary, when reading none(), I expect to somehow see an empty iterator.

16 Likes

I didn't know about .not(). That does make things a bit more ergonomic I guess. I still don't think the readability is as good though.

I also don't think that just because you can combine two existing methods doesn't mean we shouldn't add this as a convenience. We have .find_map(f) which is just .filter_map(f).next() :man_shrugging:

One could of course also use a local variable. That's very readable and everyone will understand what is going on.

1 Like

Personally I like Ruby's style of offering logical duals consistently. any has none, if has unless. Yes, one is the negation of the other, and there are a variety of ways to write code that does the same thing (negation, importing the Not trait to use .not() [although I think most folks may not be aware this is possible]), but I personally think writing none is clearer (and less likely to do wrong) than the alternatives.

Good to know I'm not alone :blush:

I don't see Ruby as "consistent" in that sense, because there's no negation of Enumerable#all?.

1 Like

There's Enumerable#none.

While I think it can be overdone (like unless), I see a benefit in offering explicitly named operations in cases like Iterator, because it's not a dual there, any/all/none form a related triple.

It's a bit like having select and reject instead of filter, because just counting humanity's time wasted on figuring out whether the argument "filters things in" or "filters things out" makes it a good idea.

Looking at other language's, it seems to be pretty common to offer all three:

  • Java: anyMatch, allMatch, noneMatch
  • Scala: exists, forall, contains

I'd say it's more common for languages to offer all three or no such operations at all; rather than two.

1 Like

if you want to be complete, then you would need something that acts like iter.all(f).not()

iter.any(f) == iter.none(f).not();
iter.none(f) == iter.any(f).not();

iter.all(f) == iter.foo(f).not();
iter.foo(f) == iter.all(f).not();
1 Like

iter.all(f).not()

none.

No, this is incorrect. iter.all(f).not() should return true if the iterator may have some elements that pass the test, but not all of the elements pass the test.

iter.all(f)  == iter.foo(f).not()  == iter.none(|x| f(x).not()) == iter.any( |x| f(x).not()).not()
iter.any(f)  == iter.none(f).not() == iter.foo( |x| f(x).not()) == iter.all( |x| f(x).not()).not()
iter.none(f) == iter.any(f).not()  == iter.all( |x| f(x).not()) == iter.foo( |x| f(x).not()).not()
iter.foo(f)  == iter.all(f).not()  == iter.any( |x| f(x).not()) == iter.none(|x| f(x).not()).not()

It's kind of like any_false where the current any is like any_true.

3 Likes

FWIW, one could replace these with their boolean equivalent:

all    ~ and 
any    ~ or
none   ~ nor
foo    ~ nand

Might even make sense to have these, next to sum and product for values.

2 Likes

Just as another data point of prior art, the C++ STL also has std::all_of, std::any_of and std::none_of. I assume that the fourth variant was deemed to rare to deserve its own function.

Just to make it even more clear, here's a little working example showing the difference between any and what I've called any_not: Code on Playground

1 Like

Teeechnically there's more:

all some none boolean name
0 0 0 false contradiction
0 0 1 nor none
0 1 0 not equal some_but_not_all
0 1 1 nand not_all
1 0 0 and all
1 0 1 equal all_or_none
1 1 0 or any / some
1 1 1 true tautology
4 Likes

Alright, here's an updated Playground example covering all the cases. Functions already provided are commented out. Two of the functions have an added Copy bound which would be avoided in a real-world implementation, but which is placed here to simplify the example: Playground example