web-dev-qa-db-ja.com

結果のイテレーターを使用する最も慣用的な方法は何ですか?

私はこのようなコードを持っています:

_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<_>, _>>_を認識しています。具体的には、次の定型文を排除しようとしています。

  • フィルターのクロージャーでは、_thing_result_でmatchを使用する必要があります。これは1行にすぎないように感じます。 .filter_if_ok(|thing| check(a))
  • mapを使用するたびに、Errの可能性に対処するために、追加のステートメントlet a = try!(thing_result);を含める必要があります。繰り返しますが、これは.map_if_ok(|thing| ...)に抽象化できると思います。

このレベルの簡潔さを得るために使用できる別のアプローチはありますか、それともそれを強化する必要があるだけですか?

15
Tim McLean

これらのイテレータは自分で実装できます。 filter および map が標準ライブラリにどのように実装されているかをご覧ください。

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>>()

遊び場

5
aSpex

これを意味する方法はたくさんあります。

パニックしたいだけの場合は、.map(|x| x.unwrap())を使用します。

すべての結果が必要な場合or単一のエラー、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());
_

多くの場合、これらの操作は以前の操作と組み合わせることができます。

26
Veedrac

Rust 1.27、したがって _Iterator::try_for_each_ は興味深いかもしれません:

イテレータの各項目に誤りのある関数を適用し、最初のエラーで停止してそのエラーを返すイテレータメソッド。

これはfor_each()の誤りのある形式、またはtry_fold()のステートレスバージョンと考えることもできます。

5
arkod

_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関数を変更して、trueではなくSome(x)を返し、falseではなくNoneを返すことができる場合は、_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_を使用しても役に立たないようです。

3
pengowen123