try_fold
实现了我的目标。然而,这需要做一些尴尬的事情,即在成功的情况下返回 Err
。我想要了解的是这是否是一种惯用的做事方式。我能想到的唯一其他方法是使用常规的 for
,或者类似 find
的东西,同时保持外部状态(这感觉更奇怪!)。我知道 clojure 中有 reduced
,但我找不到 rust 的等效项。
这是一个最小可行的例子。该示例围绕初始 Vec 循环,对每个项目进行求和,并在第一个总和大于 10 时停止。它返回 12,因为
1 + 5 - 3 + 1 + 5 - 3 + 1 + 5 = 12
:
fn main() {
let seed = vec![1, 5, -3];
let res = seed.iter().cycle().try_fold(0, |accum, value| {
let next = accum + value;
if next > 10 {
Err(next)
} else {
Ok(next)
}
});
if let Err(res) = res {
println!("{:?}", res);
} else {
unreachable!();
}
}
(游乐场链接)
让我觉得奇怪的部分是
if let Err(res) = res
是积极条件,而且实际上是摆脱循环的唯一途径(因此另一个分支无法到达)。
有“更好”的方法吗?
简单,正常迭代即可
fn main() {
let seed = vec![1, 5, -3];
let mut accum = 0;
for value in seed.iter().cycle() {
accum += value;
if accum > 10 {
break;
}
}
println!("{:?}", accum)
}
您可以使用
scan
和 find
:
fn main() {
let seed = vec![1, 5, -3];
let res = seed
.iter()
.cycle()
.scan(0, |accum, value| {
*accum += value;
Some(*accum)
})
.find(|&x| x > 10)
.unwrap();
println!("{:?}", res);
}
这实际上比使用
fold_while
中的 itertools
稍短,看起来像这样:
use itertools::{FoldWhile::{Continue, Done}, Itertools};
fn main() {
let seed = vec![1, 5, -3];
let res = seed
.iter()
.cycle()
.fold_while(0, |accum, value| {
let next = accum + value;
if next > 10 {
Done(next)
} else {
Continue(next)
}
})
.into_inner();
println!("{:?}", res);
}
如果您不介意使用完善的外部库,请考虑使用itertools::fold_while。如果您喜欢这种编程风格,可能还有其他有用的扩展。
截至今天,您的问题还没有明显的答案,这实际上非常令人惊讶。就像 @peter-hall 所建议的那样,
scan
是问题的惯用函数式答案,但 Rust 的 scan
被认为是很难用。
在我看来,
try_fold
仍然是最生锈的选择。以下是我的综合建议,可以使循环折叠更令人满意(选择一个或多个更改!):
let res = {
use Result::{Err as Break, Ok as Next};
seed.iter()
.cycle()
.try_fold(0, |accum, value| match accum + value {
res if res > 10 => Break(res),
next => Next(next),
})
.unwrap_err()
};
在我看来,使用
match
比使用 if
/else
更有助于命名案例。 Result
类型别名还提供了一些描述中断缩进的语义。
如果你不想用
unwrap_err
,有人建议用代替
let res = iter.try_fold(...).unwrap_or_else(|res| res);
也可以使用无可辩驳的模式:
let (Ok(res) | Err(res)) = iter.try_fold(...);
如果是我自己的代码,我会让事情尽可能简单和普通:
let res = seed
.iter()
.cycle()
.try_fold(0, |accum, value| match accum + value {
res if res > 10 => Err(res),
acc => Ok(acc),
})
.unwrap_err();
阅读
.unwrap_err()
对我来说已经足够清楚了,因为这意味着 Err
是我们期望的返回类型。这是我最好的成绩,加油!
也可以用
iter::successors
来实现。
use std::iter;
let seed: Vec<i32> = vec![1, 5, -3];
let mut cycle = seed.into_iter().cycle();
let mut acc_iter = iter::successors(cycle.next(), |n| Some(n + cycle.next()?));
let result = acc_iter.find(|&n| n > 10);
println!("{:?}", result);
// if it's not ok to take ownership of the `seed`,
// you might have to explicitly copy the first value for `successors`
//
// let mut cycle = seed.iter().cycle();
// let mut acc_iter = iter::successors(cycle.next().map(|n| *n), |n| Some(n + cycle.next()?));
// let result = acc_iter.find(|&n| n > 10);
另一种选择是使用
iter::from_fn
:
let mut cycle = seed.iter().cycle();
let mut acc = 0;
let mut acc_iter = iter::from_fn(|| {
acc += cycle.next()?;
Some(acc)
});
let result = acc_iter.find(|&n| n > 10);
println!("{:?}", result);
我想说 ControlFlow 枚举就是为此而设计的。 文档中甚至还有一个将其与
try_fold
一起使用的示例。
您的示例将如下所示:
let seed = vec![1, 5, -3];
use ControlFlow::{Break, Continue};
seed.iter()
.cycle()
.try_fold(0, |accum, value| {
let next = accum + value;
if next > 10 {
Break(next)
} else {
Continue(next)
}
})
.break_value()
.unwrap()
break_value()
是实验性,这意味着您仍然必须匹配结果或每晚使用。