私はこのようなコードを持っています:
_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| ...)
に抽象化できると思います。このレベルの簡潔さを得るために使用できる別のアプローチはありますか、それともそれを強化する必要があるだけですか?
これらのイテレータは自分で実装できます。 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>>()
これを意味する方法はたくさんあります。
パニックしたいだけの場合は、.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());
_
多くの場合、これらの操作は以前の操作と組み合わせることができます。
Rust 1.27、したがって _Iterator::try_for_each
_ は興味深いかもしれません:
イテレータの各項目に誤りのある関数を適用し、最初のエラーで停止してそのエラーを返すイテレータメソッド。
これは
for_each()
の誤りのある形式、またはtry_fold()
のステートレスバージョンと考えることもできます。
_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
_を使用しても役に立たないようです。