-
Notifications
You must be signed in to change notification settings - Fork 310
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement custom fold for WhileSome
#780
Implement custom fold for WhileSome
#780
Conversation
src/adaptors/mod.rs
Outdated
fn fold<B, F>(self, acc: B, f: F) -> B | ||
where | ||
Self: Sized, | ||
F: FnMut(B, Self::Item) -> B, | ||
{ | ||
self.iter | ||
.take_while(|opt| opt.is_some()) | ||
.map(|item| item.unwrap()) | ||
.fold(acc, f) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's possible to implement this without the unwrap
, using try_fold
:
fn fold<B, F>(self, acc: B, f: F) -> B | |
where | |
Self: Sized, | |
F: FnMut(B, Self::Item) -> B, | |
{ | |
self.iter | |
.take_while(|opt| opt.is_some()) | |
.map(|item| item.unwrap()) | |
.fold(acc, f) | |
} | |
fn fold<B, F>(mut self, init: B, mut f: F) -> B | |
where | |
Self: Sized, | |
F: FnMut(B, Self::Item) -> B, | |
{ | |
// Replace with `ControlFlow`, once MSRV >= 1.55.0 | |
use core::result::Result::{Ok as Continue, Err as Break}; | |
let res = self.iter.try_fold(init, |acc, item| match item { | |
Some(item) => Continue(f(acc, item)), | |
None => Break(acc), | |
}); | |
let (Break(res) | Continue(res)) = res; | |
res | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jswrenn Thanks for your feedback! I'm a bit hesitate to override fold
with try_fold
though.
The names of the methods in Rust are usually semantically clear. When overriding fold
, it makes sense to delegate its responsibility to the underlying iterator's fold
. Similarly, try_fold
should be reserved for overriding try_fold
.
What do you think ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the Rust standard library, it's quite common for fold
to be implemented in terms of try_fold
. In your original code you construct a TakeWhile
and call fold
on it. But TakeWhile
's fold implementation delegates to try_fold
!
The general rule to follow is something like this:
- If you need to completely consume an iterator, use
for_each
orfold
.- Use
for_each
if you don't need to maintain an accumulator. - Use
fold
if you do need to maintain an accumulator.
- Use
- If you need to partly consume an iterator, use
try_for_each
ortry_fold
.- Same rules about accumulators apply.
Here, try_fold
is the perfect tool: We need to consume the iterator partially, up until the first None
, with an accumulator.
WhileSome
Untested, I think the bench part should be
|
Interestingly, when I re-benchmark again using the revised benchmark (using both default: time: [59.414 ns 59.554 ns 59.697 ns] Seems that the overhead from calling |
Each time in |
Implement custom fold for while some
2808bcc
to
2a59240
Compare
I guess I can't merge this because an owner (jswrenn) requested changes. |
#755
This PR introduces a custom fold method for
WhileSome
. The optimization hinges on the underlying iterator'sfold
:With Custom
fold
: If the underlying iterator provides its own specializedfold
, the performance gain depends on its implementation.Without Custom
fold
: In the absence of a specialized fold for the underlying iterator, the performance remains on par with the default.Benchmark Results:
Default fold: time: [497.09 ns 497.45 ns 497.82 ns]
Custom fold: time: [456.09 ns 458.15 ns 460.80 ns]