This method will act as filter with complement of provided predicate.
If function defined as is_something(&value) -> bool {...} (which is a common way) and we'd like to pick values which are opposite, we need to create unnecessary closure .filter(|value| !is_something(value)) but instead we could simply write .reject(is_something).
It's also very useful when condition is a complex bool expression
use chrono::DateTime;
use chrono::Utc;
struct Letter {
date: DateTime<Utc>,
text: String,
sender: String,
receiver: String,
}
fn is_spam(Letter { text, .. }: &Letter) -> bool {
text.to_lowercase().contains("promotion")
}
fn filter_letters<'a>(
letters: &'a [Letter],
date: DateTime<Utc>,
sender_blacklist: &[&str]
) -> Vec<&'a Letter> {
letters
.into_iter()
.filter(|letter| {
letter.sender != letter.receiver
&& letter.date >= date
&& !sender_blacklist
.into_iter()
.any(|sender| letter.sender == *sender)
&& !is_spam(letter)
})
.collect()
}
fn filter_letters_using_reject<'a>(
letters: &'a [Letter],
date: DateTime<Utc>,
sender_blacklist: &[&str]
) -> Vec<&'a Letter> {
letters
.into_iter()
.reject(|letter| {
letter.sender == letter.receiver
|| letter.date < date
|| sender_blacklist
.into_iter()
.any(|sender| letter.sender == *sender)
|| is_spam(letter)
})
.collect()
}
fn main() {
let letters: Vec<Letter> = vec![];
let filtered_letters = filter_letters(
&letters,
Utc::now() - chrono::Duration::weeks(2),
&["my_enemy"]
);
let filtered_from_spam_letters = letters.iter().reject(is_spam).collect::<Vec<_>>();
}
The first function reads as
take if is not and if (greater or equal to) and is not and is not
which is much less clear than
drop if is or less than or is or is
Moreover there's a partition function which acts like a combination of filter and reject but we don't actually have reject.
That seems like a spotfix for just one function that takes predicates, a more general solution would be adding a trait for predicate functions that provides .negate() on function pointers.
Prior art: the java standard library function interface for predicates
In my opinion, .negate() function will only make code less readable because to apply it in place we should write
let odds = iter.filter((|&a| a % 2 == 0).negate()) // which is much less readable than
let odds = iter.filter(|&a| a % 2 != 0) // and
let odds = iter.filter(|&a| !(a % 2 == 0))
If apply to defined function, it still will lose to |v| !f(v)
let odds = iter.filter(is_even.negate());
let odds = iter.filter(|v| !is_even(v));
I didn't write any code on Java for many many years and I don't know anything about its current status, but as far as I know most of modern JVM-compatible languages have the same reject function:
Kotlin filterNot
Scala filterNot
Clojure remove
Iterator trait contains 11 functions which take predicate - all, any, filter, skip_while, take_while, partition_in_place, partition, is_partitioned , find, position, rposition.
I think it's obvious why any, find, partition_in_place, partition, is_partitioned,position, rposition, skip_while/ take_while don't suggest to have opposite versions.
So we have two functions all and filter. all may have opposite function none which checks that no elements match given predicate and filter may have reject function as described above.
In many languages it's called filterNot but I'd prefer reject because filter_not looks a bit bulky.
reject seems to be the result of languages convenience evolution - for example, in Erlang there's no such function but its modern version Elixir has it https://hexdocs.pm/elixir/Stream.html#reject/2.
I think it's obvious why any, find [... ]don't suggest to have opposite versions.
While all could be negated by a universal quanitification none there could also be a negated existential quantification as the opposite for any, it would check that there exists at least one element where the predicate doesn't hold true.
Similarly one could find the first element that does not match a predicate, if any.
At least logically those are valid constructs, perhaps they don't have many uses in practice, the point is that the concern of negation is one of the predicate, not of the iterator.
If apply to defined function, it still will lose to |v| !f(v)
let odds = iter.filter(is_even.negate()); let odds = iter.filter(|v| !is_even(v));
Well, perhaps it could be implemented as std::ops::Not? Then you could either
.filter(is_even.not())
.filter(not(is_even))
.filter(!is_even)
This could be further extended to other boolean operators, then has the option to either use operators or function calls in prefix or infix position to combine predicates.
.filter(divisible_by_three.or(divisible_by_five))
.filter(or(divisible_by_three, divisible_by_five))
.filter(divisible_by_three || divisible_by_five)
I think this also has the advantage that these types can be named and could be specialized on.
If anyone wants this, I suggest they just make a PR. This is a small thing that just needs some libs teams eyes on it to see what they think. (I'm personally not a fan of this, and would rather invert whatever's in the closure, but it's not up to me.)