-
Notifications
You must be signed in to change notification settings - Fork 12.9k
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
Add std::iter::unfold #55869
Add std::iter::unfold #55869
Conversation
r? @aidanhs (rust_highfive has picked a reviewer for you, use r? to override) |
For another example, the other somewhat-general purpose iterator that I initially considered submitting can be expressed on top of fn successors<T, F>(mut next: Option<T>, mut succ: F) -> impl Iterator<Item = T>
where
F: FnMut(&T) -> Option<T>,
{
std::iter::iterate(move || {
next.take().map(|item| {
next = succ(&item);
item
})
})
} Edit: with explicit state: fn successors<T, F>(first: Option<T>, mut succ: F) -> impl Iterator<Item = T>
where
F: FnMut(&T) -> Option<T>,
{
std::iter::unfold(first, move |next| {
next.take().map(|item| {
*next = succ(&item);
item
})
})
} |
NB: this is closer to Haskell’s One thing that could be tweaked about this function is making the state explicit instead of implicit in the closure as it is now. That would make the function’s signature
and the motivating example look like this: fn counter() -> impl Iterator<Item=usize> {
std::iter::iterate(0, move |count| {
if count < 6 {
Some((count + 1, count + 1))
} else {
None
}
})
} which, among other things, would make this strictly more composable and flexible. It would also be closer design-wise to our other iterators (esp. |
What does the How is explicitly state more composable or flexible? Returning an (optional) tuple feels kinda awkward to me. |
Associativity matters more in Haskell than it does in Rust, though, so I think there’s little reason to worry about the associativity here.
At the very least it is possible to use non-closures with the function. I agree with the tuple feeling slightly more awkward than captured state in this specific example, but to me it seems that given a different example it could go the other way as well. All that being said, I feel that |
If it will be possible to use generators to create iterators, would this function still be useful? |
@ljedrz In my opinion we’ve been very conservative in the past with the standard library, especially in the months before and after 1.0. You can see that the given deprecation reason can be summarized as "Meh." @bluetech Indeed it would be less useful. However as far as I know such generators (or generators at all, other than as an unstable implementation detail of I’ve rename to |
The job Click to expand the log.
I'm a bot! I can only do what humans tell me to, so if this was not helpful or you have suggestions for improvements, please ping or otherwise contact |
@SimonSapin don't get me wrong, I'm a big fan of the idea :). |
I'm worried that this will discourage people providing proper size hints for their iterators. This is also very similar to |
src/libcore/iter/sources.rs
Outdated
@@ -386,3 +386,68 @@ impl<T> FusedIterator for Once<T> {} | |||
pub fn once<T>(value: T) -> Once<T> { | |||
Once { inner: Some(value).into_iter() } | |||
} | |||
|
|||
/// Creates a new iterator where each iteration calls the provided closure | |||
/// `F: FnMut() -> Option<T>`. |
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.
/// `F: FnMut() -> Option<T>`. | |
/// `F: FnMut(&mut St) -> Option<T>`. |
/// without using the more verbose syntax of creating a dedicated type | ||
/// and implementing the `Iterator` trait for it. | ||
/// Iterator state can be kept in the closure’s captures and environment. | ||
/// |
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.
/// | |
/// An initial state can also be passed in the first argument of `unfold`. | |
/// |
src/libcore/iter/sources.rs
Outdated
/// ``` | ||
/// #![feature(iter_unfold)] | ||
/// let counter = std::iter::unfold(0, |count| { | ||
/// // increment our count. This is why we started at zero. |
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.
/// // increment our count. This is why we started at zero. | |
/// // Increment our count. This is why we started at zero. |
(should be fixed in module level docs also...)
src/libcore/iter/sources.rs
Outdated
/// // increment our count. This is why we started at zero. | ||
/// *count += 1; | ||
/// | ||
/// // check to see if we've finished counting or not. |
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.
/// // check to see if we've finished counting or not. | |
/// // Check to see if we've finished counting or not. |
src/libcore/iter/sources.rs
Outdated
} | ||
} | ||
|
||
/// An iterator where each iteration calls the provided closure `F: FnMut() -> Option<T>`. |
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.
/// An iterator where each iteration calls the provided closure `F: FnMut() -> Option<T>`. | |
/// An iterator where each iteration calls the provided closure | |
/// `F: FnMut(&mut St) -> Option<T>`. |
/// } | ||
/// }); | ||
/// assert_eq!(counter.collect::<Vec<_>>(), &[1, 2, 3, 4, 5]); | ||
/// ``` |
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.
Maybe leave a note that this iterator is not fused and that calling it after it returned None
the first time might return Some
afterwards.
src/libcore/iter/sources.rs
Outdated
#[unstable(feature = "iter_unfold", issue = /* FIXME */ "0")] | ||
pub struct Unfold<St, F> { | ||
/// The current state of the iterator | ||
pub state: St, |
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.
Double check: are we comfortable with exposing a public field at this point in time?
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.
It was public in the old Unfold
that we removed in 1.3 and I didn’t see a reason for it not to be. But I’ve undone this change for now in case it makes anyone feel better.
src/libcore/iter/sources.rs
Outdated
/// See its documentation for more. | ||
/// | ||
/// [`unfold`]: fn.unfold.html | ||
#[derive(Copy, Clone, Debug)] |
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.
This will add an impl Debug for Unfold<St, F> where St: Debug, F: Debug
. Note in particular F: Debug
; this is probably not what the user wants so I think the Debug
implementation should be hand rolled in this instance.
We do this all over the place with iterators, for example:
impl<I: Debug, P> Debug for Filter<I, P>
No P: Debug
here. ;)
I think this is a good idea and I agree with @SimonSapin that generators aren't sufficient justification to skip this given that generators haven't even been RFC accepted yet (experimental don't count) and that they thus are far into the future. The design seems mostly right to me and the name is also good (follows the Haskell theme we generally have for iterators). @clarcharr I think it's fine to not provide proper size hints for your iterator; premature optimization and all that... First things first, prototype your application and make it work correctly first, then after, if profiling shows up a problem, then you tune things with a custom iterator. |
/// } | ||
/// }); | ||
/// assert_eq!(counter.collect::<Vec<_>>(), &[1, 2, 3, 4, 5]); | ||
/// ``` |
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.
Maybe also leave a note about size_hint
.
I’ve added a note about FWIW in the use case that triggered this PR (tree traversal) there is no cheap way to compute a |
FWIW, itertools picked up |
On reading the signature here my first reaction was "why take a state parameter because the closure env also has state?" but it looks like that's how this function started and it's ended up here with a state parameter. On reading the discussion here I'm not quite sure why, but @SimonSapin how come you made the switch? (or @cuviper, do you know why it's the way it is in |
I believe @bluss just took the I guess the state parameter can make it easier to write a single-expression In some sense, the |
Note that we already can avoid creating a fn counter() -> impl Iterator<Item=i32> {
let mut state = 0;
std::iter::repeat(())
.scan((), move |(), ()| {
state += 1;
if state < 6 {
Some(state)
} else {
None
}
})
} |
I personally really enjoy Kotlin's fn counter() -> impl Iterator<Item=usize> {
generate(Some(1), |it| {
if it + 1 < 6 { Some(it + 1) } else { None }
})
} I'd rather we add |
@alexcrichton I initially liked the simplicity of the
… but I’m ok with switch back if other people prefer the more "trivial" behavior. @cuviper Indeed, re @matklad I think this |
Agree, didn't saw that comment. From my experience though, Probably a good thing to do would be to grep something like |
Implicit state seems more elegant, but I don't use unfold enough in the wild to really know what works best. What is awkward about the I think fold has a good reason to use an explicit accumulator: when ownership is passed through each accumulation. |
(FWIW Servo does not use |
BTW, I didn't see your original code, but pub fn iterate<T, F>(
seed: T,
f: F,
) -> Unfold<(F, Option<T>, bool), fn(&mut (F, Option<T>, bool)) -> Option<T>>
where
T: Clone,
F: FnMut(T) -> T, And there's a similar pub fn iterate<St, F>(initial_value: St, f: F) -> Iterate<St, F>
where
F: FnMut(&St) -> St, But unlike @SimonSapin's |
Hmm, I probably shouldn’t have squashed the commits. The original code was something like this: impl<T, F: FnMut() -> Option<T>> Iterator for Foo<F> {
type Item = T;
fn next(&mut self) -> Option<T> {
(self.0)()
}
} |
I’ve optimistically created a tracking issue at #55977.
I’ve added |
Bikeshed: |
Ok these seem reasonable enough to me to add unstable, r=me with @Centril's doc comments as well |
@bors r=alexcrichton |
📌 Commit a4279a0 has been approved by |
Add std::iter::unfold This adds an **unstable** ~`std::iter::iterate`~ `std::iter::unfold` function and ~`std::iter::Iterate`~ `std::iter::Unfold` type that trivially wrap a ~`FnMut() -> Option<T>`~ `FnMut(&mut State) -> Option<T>` closure to create an iterator. ~Iterator state can be kept in the closure’s environment or captures.~ This is intended to help reduce amount of boilerplate needed when defining an iterator that is only created in one place. Compare the existing example of the `std::iter` module: (explanatory comments elided) ```rust struct Counter { count: usize, } impl Counter { fn new() -> Counter { Counter { count: 0 } } } impl Iterator for Counter { type Item = usize; fn next(&mut self) -> Option<usize> { self.count += 1; if self.count < 6 { Some(self.count) } else { None } } } ``` … with the same algorithm rewritten to use this new API: ```rust fn counter() -> impl Iterator<Item=usize> { std::iter::unfold(0, |count| { *count += 1; if *count < 6 { Some(*count) } else { None } }) } ``` ----- This also add unstable `std::iter::successors` which takes an (optional) initial item and a closure that takes an item and computes the next one (its successor). ```rust let powers_of_10 = successors(Some(1_u16), |n| n.checked_mul(10)); assert_eq!(powers_of_10.collect::<Vec<_>>(), &[1, 10, 100, 1_000, 10_000]); ```
Rollup of 14 pull requests Successful merges: - #55767 (Disable some pretty-printers when gdb is rust-enabled) - #55838 (Fix #[cfg] for step impl on ranges) - #55869 (Add std::iter::unfold) - #55945 (Ensure that the argument to `static_assert` is a `bool`) - #56022 (When popping in CTFE, perform validation before jumping to next statement to have a better span for the error) - #56048 (Add rustc_codegen_ssa to sysroot) - #56091 (Fix json output in the self-profiler) - #56097 (Fix invalid bitcast taking bool out of a union represented as a scalar) - #56116 (ci: Download clang/lldb from tarballs) - #56120 (Add unstable Literal::subspan().) - #56154 (Pass additional linker flags when targeting Fuchsia) - #56162 (std::str Adapt documentation to reality) - #56163 ([master] Backport 1.30.1 release notes) - #56168 (Fix the tracking issue for hash_raw_entry) Failed merges: r? @ghost
This adds an unstable
std::iter::iterate
std::iter::unfold
function andstd::iter::Iterate
std::iter::Unfold
type that trivially wrap aFnMut() -> Option<T>
FnMut(&mut State) -> Option<T>
closure to create an iterator.Iterator state can be kept in the closure’s environment or captures.This is intended to help reduce amount of boilerplate needed when defining an iterator that is only created in one place. Compare the existing example of the
std::iter
module: (explanatory comments elided)… with the same algorithm rewritten to use this new API:
This also add unstable
std::iter::successors
which takes an (optional) initial item and a closure that takes an item and computes the next one (its successor).