Auto merge of #49098 - matklad:find_map, r=KodrAus
Add Iterator::find_map I'd like to propose to add `find_map` method to the `Iterator`: an occasionally useful utility, which relates to `filter_map` in the same way that `find` relates to `filter`. `find_map` takes an `Option`-returning function, applies it to the elements of the iterator, and returns the first non-`None` result. In other words, `find_map(f) == filter_map(f).next()`. Why do we want to add a function to the `Iterator`, which can be trivially expressed as a combination of existing ones? Observe that `find(f) == filter(f).next()`, so, by the same logic, `find` itself is unnecessary! The more positive argument is that desugaring of `find[_map]` in terms of `filter[_map]().next()` is not super obvious, because the `filter` operation reads as if it is applies to the whole collection, although in reality we are interested only in the first element. That is, the jump from "I need a **single** result" to "let's use a function which maps **many** values to **many** values" is a non-trivial speed-bump, and causes friction when reading and writing code. Does the need for `find_map` arise in practice? Yes! * Anecdotally, I've more than once searched the docs for the function with `[T] -> (T -> Option<U>) -> Option<U>` signature. * The direct cause for this PR was [this](https://github.com/rust-lang/cargo/pull/5187/files/1291c50e86ed4b31db0c76de03a47a5d0074bbd7#r174934173) discussion in Cargo, which boils down to "there's some pattern that we try to express here, but current approaches looks non-pretty" (and the pattern is `filter_map` * There are several `filter_map().next` combos in Cargo: [[1]](545a4a2c93/src/cargo/ops/cargo_new.rs (L585)), [[2]](545a4a2c93/src/cargo/core/resolver/mod.rs (L1130)), [[3]](545a4a2c93/src/cargo/ops/cargo_rustc/mod.rs (L1086)). * I've also needed similar functionality in `Kotlin` several times. There, it is expressed as `mapNotNull {}.firstOrNull`, as can be seen [here](ee8bdb4e07/src/main/kotlin/org/rust/cargo/project/model/impl/CargoProjectImpl.kt (L154)), [here](ee8bdb4e07/src/main/kotlin/org/rust/lang/core/resolve/ImplLookup.kt (L444)) [here](ee8bdb4e07/src/main/kotlin/org/rust/ide/inspections/RsLint.kt (L38)) and [here](ee8bdb4e07/src/main/kotlin/org/rust/cargo/toolchain/RustToolchain.kt (L74)) (and maybe in some other cases as well) Note that it is definitely not among the most popular functions (it definitely is less popular than `find`), but, for example it (in case of Cargo) seems to be more popular than `rposition` (1 occurrence), `step_by` (zero occurrences) and `nth` (three occurrences as `nth(0)` which probably should be replaced with `next`). Do we necessary need this function in `std`? Could we move it to itertools? That is possible, but observe that `filter`, `filter_map`, `find` and `find_map` together really form a complete table: ||| |-------|---------| | filter| find| |filter_map|find_map| It would be somewhat unsatisfying to have one quarter of this table live elsewhere :) Also, if `Itertools` adds an `find_map` method, it would be more difficult to move it to std due to name collision. Hm, at this point I've searched for `filter_map` the umpteenth time, and, strangely, this time I do find this RFC: https://github.com/rust-lang/rfcs/issues/1801. I guess this could be an implementation though? :) To sum up: Pro: - complete the symmetry with existing method - codify a somewhat common non-obvious pattern Contra: - niche use case - we can, and do, live without it
This commit is contained in:
commit
577d29c10a
3 changed files with 60 additions and 0 deletions
|
|
@ -1745,6 +1745,38 @@ pub trait Iterator {
|
|||
}).break_value()
|
||||
}
|
||||
|
||||
/// Applies function to the elements of iterator and returns
|
||||
/// the first non-none result.
|
||||
///
|
||||
/// `iter.find_map(f)` is equivalent to `iter.filter_map(f).next()`.
|
||||
///
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// #![feature(iterator_find_map)]
|
||||
/// let a = ["lol", "NaN", "2", "5"];
|
||||
///
|
||||
/// let mut first_number = a.iter().find_map(|s| s.parse().ok());
|
||||
///
|
||||
/// assert_eq!(first_number, Some(2));
|
||||
/// ```
|
||||
#[inline]
|
||||
#[unstable(feature = "iterator_find_map",
|
||||
reason = "unstable new API",
|
||||
issue = "49602")]
|
||||
fn find_map<B, F>(&mut self, mut f: F) -> Option<B> where
|
||||
Self: Sized,
|
||||
F: FnMut(Self::Item) -> Option<B>,
|
||||
{
|
||||
self.try_for_each(move |x| {
|
||||
match f(x) {
|
||||
Some(x) => LoopState::Break(x),
|
||||
None => LoopState::Continue(()),
|
||||
}
|
||||
}).break_value()
|
||||
}
|
||||
|
||||
/// Searches for an element in an iterator, returning its index.
|
||||
///
|
||||
/// `position()` takes a closure that returns `true` or `false`. It applies
|
||||
|
|
|
|||
|
|
@ -1146,6 +1146,33 @@ fn test_find() {
|
|||
assert!(v.iter().find(|&&x| x % 12 == 0).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_find_map() {
|
||||
let xs: &[isize] = &[];
|
||||
assert_eq!(xs.iter().find_map(half_if_even), None);
|
||||
let xs: &[isize] = &[3, 5];
|
||||
assert_eq!(xs.iter().find_map(half_if_even), None);
|
||||
let xs: &[isize] = &[4, 5];
|
||||
assert_eq!(xs.iter().find_map(half_if_even), Some(2));
|
||||
let xs: &[isize] = &[3, 6];
|
||||
assert_eq!(xs.iter().find_map(half_if_even), Some(3));
|
||||
|
||||
let xs: &[isize] = &[1, 2, 3, 4, 5, 6, 7];
|
||||
let mut iter = xs.iter();
|
||||
assert_eq!(iter.find_map(half_if_even), Some(1));
|
||||
assert_eq!(iter.find_map(half_if_even), Some(2));
|
||||
assert_eq!(iter.find_map(half_if_even), Some(3));
|
||||
assert_eq!(iter.next(), Some(&7));
|
||||
|
||||
fn half_if_even(x: &isize) -> Option<isize> {
|
||||
if x % 2 == 0 {
|
||||
Some(x / 2)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_position() {
|
||||
let v = &[1, 3, 9, 27, 103, 14, 11];
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@
|
|||
#![feature(atomic_nand)]
|
||||
#![feature(reverse_bits)]
|
||||
#![feature(inclusive_range_fields)]
|
||||
#![feature(iterator_find_map)]
|
||||
|
||||
extern crate core;
|
||||
extern crate test;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue