Какой самый идиоматичный способ работы с итератором результатов?

У меня такой код:

let things = vec![/* ...*/]; // e.g. Vec<String>
things
    .map(|thing| {
        let a = try!(do_stuff(thing));
        Ok(other_stuff(a))
    })
    .filter(|thing_result| match *thing_result {
        Err(e) => true,
        Ok(a) => check(a),
    })
    .map(|thing_result| {
        let a = try!(thing_result);
        // do stuff
        b
    })
    .collect::<Result<Vec<_>, _>>()

С точки зрения семантики я хочу прекратить обработку после первой ошибки.

Приведенный выше код работает, но кажется довольно громоздким. Есть ли способ лучше? Я просмотрел документы в поисках чего-то вроде filter_if_ok, но ничего не нашел.

Я знаю collect::<Result<Vec<_>, _>>, и он отлично работает. Я специально пытаюсь удалить следующий шаблон:

  • В закрытии фильтра я должен использовать match на thing_result. Я чувствую, что это должно быть просто однострочное, например .filter_if_ok(|thing| check(a)).
  • Каждый раз, когда я использую map, я должен включать дополнительный оператор let a = try!(thing_result);, чтобы иметь дело с возможностью Err. Опять же, я чувствую, что это можно абстрагировать в .map_if_ok(|thing| ...).

Есть ли другой подход, который я могу использовать для достижения такого уровня краткости, или мне просто нужно с этим справиться?


person Tim McLean    schedule 02.04.2016    source источник
comment
stackoverflow.com/questions/26368288/?   -  person ArtemGr    schedule 02.04.2016


Ответы (4)


Вы можете реализовать эти итераторы самостоятельно. Посмотрите, как _1 _ и map < / a> реализованы в стандартной библиотеке.

map_ok реализация:

#[derive(Clone)]
pub struct MapOkIterator<I, F> {
    iter: I,
    f: F,
}

impl<A, B, E, I, F> Iterator for MapOkIterator<I, F>
where
    F: FnMut(A) -> B,
    I: Iterator<Item = Result<A, E>>,
{
    type Item = Result<B, E>;

    #[inline]
    fn next(&mut self) -> Option<Self::Item> {
        self.iter.next().map(|x| x.map(&mut self.f))
    }
}

pub trait MapOkTrait {
    fn map_ok<F, A, B, E>(self, func: F) -> MapOkIterator<Self, F>
    where
        Self: Sized + Iterator<Item = Result<A, E>>,
        F: FnMut(A) -> B,
    {
        MapOkIterator {
            iter: self,
            f: func,
        }
    }
}

impl<I, T, E> MapOkTrait for I
where
    I: Sized + Iterator<Item = Result<T, E>>,
{
}

filter_ok почти то же самое:

#[derive(Clone)]
pub struct FilterOkIterator<I, P> {
    iter: I,
    predicate: P,
}

impl<I, P, A, E> Iterator for FilterOkIterator<I, P>
where
    P: FnMut(&A) -> bool,
    I: Iterator<Item = Result<A, E>>,
{
    type Item = Result<A, E>;

    #[inline]
    fn next(&mut self) -> Option<Result<A, E>> {
        for x in self.iter.by_ref() {
            match x {
                Ok(xx) => if (self.predicate)(&xx) {
                    return Some(Ok(xx));
                },
                Err(_) => return Some(x),
            }
        }
        None
    }
}

pub trait FilterOkTrait {
    fn filter_ok<P, A, E>(self, predicate: P) -> FilterOkIterator<Self, P>
    where
        Self: Sized + Iterator<Item = Result<A, E>>,
        P: FnMut(&A) -> bool,
    {
        FilterOkIterator {
            iter: self,
            predicate: predicate,
        }
    }
}

impl<I, T, E> FilterOkTrait for I
where
    I: Sized + Iterator<Item = Result<T, E>>,
{
}

Ваш код может выглядеть так:

["1", "2", "3", "4"]
    .iter()
    .map(|x| x.parse::<u16>().map(|a| a + 10))
    .filter_ok(|x| x % 2 == 0)
    .map_ok(|x| x + 100)
    .collect::<Result<Vec<_>, std::num::ParseIntError>>()

детская площадка

person aSpex    schedule 02.04.2016
comment
vec![Ok(1),Ok(2),Err(3),Ok(4)].into_iter().... выглядит лучше, но создание вектора из массива обходится дороже - person aSpex; 03.04.2016

Вы могли иметь это в виду по-разному.

Если вы просто хотите запаниковать, используйте .map(|x| x.unwrap()).

Если вы хотите, чтобы все результаты или содержали одну ошибку, collect в Result<X<T>>:

let results: Result<Vec<i32>, _> = result_i32_iter.collect();

Если вам нужно все, кроме ошибок, используйте .filter_map(|x| x.ok()) или .flat_map(|x| x).

Если вы хотите, чтобы все до первой ошибки, используйте .scan((), |_, x| x.ok()).

let results: Vec<i32> = result_i32_iter.scan((), |_, x| x.ok());

Обратите внимание, что эти операции во многих случаях можно комбинировать с более ранними операциями.

person Veedrac    schedule 02.04.2016
comment
Проголосовали за рекомендацию метода filter_map. Не знал об этом, это здорово! - person Per Lundberg; 12.05.2017
comment
Альтернативой scan, использованной выше, будет take_while(Result::is_ok).map(Result::unwrap) - person goertzenator; 15.05.2018
comment
@goertzenator Лучше избегать панического кода, если есть альтернатива. - person Veedrac; 15.05.2018
comment
Я напрягся, чтобы написать редуктор, который возвращал бы первый результат ошибки или полный вектор ... но .collect() потрясающе! - person Tails; 12.07.2020

Начиная с Rust 1.27, Iterator::try_for_each может представлять интерес. :

Метод итератора, который применяет ошибочную функцию к каждому элементу в итераторе, останавливается при первой ошибке и возвращает эту ошибку.

Это также можно рассматривать как ошибочную форму for_each() или версию try_fold() без сохранения состояния.

person arkod    schedule 20.07.2018

filter_map можно использовать для сокращения числа простых случаев. отображения и фильтрации. В вашем примере есть некоторая логика фильтра, поэтому я не думаю, что это упрощает ситуацию. К сожалению, я не вижу полезных функций в документации для Result. Я думаю, что ваш пример настолько идиоматичен, насколько это возможно, но вот несколько небольших улучшений:

let things = vec![...]; // e.g. Vec<String>
things.iter().map(|thing| {
     // The ? operator can be used in place of try! in the nightly version of Rust
    let a = do_stuff(thing)?;
    Ok(other_stuff(a))
// The closure braces can be removed if the code is a single expression
}).filter(|thing_result| match *thing_result {
        Err(e) => true,
        Ok(a) => check(a),
    }
).map(|thing_result| {
    let a = thing_result?;
    // do stuff
    b
})

Оператор ? в некоторых случаях может быть менее читабельным, поэтому вы можете не захотеть его использовать.

Если вы можете изменить функцию check, чтобы она возвращала Some(x) вместо true и None вместо false, вы можете использовать filter_map:

let bar = things.iter().filter_map(|thing| {
    match do_stuff(thing) {
        Err(e) => Some(Err(e)),
        Ok(a) => {
            let x = other_stuff(a);
            if check_2(x) {
                Some(Ok(x))
            } else {
                None
            }
        }
    }
}).map(|thing_result| {
    let a = try!(thing_result);
    // do stuff
    b
}).collect::<Result<Vec<_>, _>>();

Вы также можете избавиться от let a = try!(thing);, используя совпадение в некоторых случаях. Однако использование filter_map здесь не помогает.

person pengowen123    schedule 02.04.2016