Skip to content

Add an Iterate iterator #15507

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

Merged
merged 1 commit into from Jul 13, 2014
Merged

Add an Iterate iterator #15507

merged 1 commit into from Jul 13, 2014

Conversation

ghost
Copy link

@ghost ghost commented Jul 7, 2014

The new iterator takes a function and produces an infinite stream
of results of repeated applications of the function, starting from
the provided seed value.

@ghost
Copy link
Author

ghost commented Jul 7, 2014

Needless to say, I'm not too happy about the name.

fn next(&mut self) -> Option<A> {
let last_arg = unsafe { mem::zeroed() };
mem::swap(&mut last_arg, &mut self.next_arg);
self.next_arg = (self.f)(last_arg.clone());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may not be failure-safe because any failure will attempt to drop the zeroed next_arg field. Why not store Option<A>?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right! I changed it to use Option (and noticed I couldn't use Option::mutate as it takes a bare function, which maybe it shouldn't?).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and noticed I couldn't use Option::mutate as it takes a bare function, which maybe it shouldn't

It takes a closure AFAICT: Option.mutate?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@huonw Oh dear, yes. I got really confused by the error message. Learning Rust every day!

@bluss
Copy link
Member

bluss commented Jul 7, 2014

It is similar but less general than the already present std::iter::Unfold. Less general means that it's easier to use, of course.

// Doubling sequence: 1, 2, 4, 8, ..
std::iter::Unfold::new(1i, |st| {let elt = *st; *st *= 2; Some(elt) });

@lilyball
Copy link
Contributor

lilyball commented Jul 7, 2014

This is nearly identical to Unfold except that

  1. The closure returns T instead of Option<T>, which means it's strictly less powerful
  2. The initial seed is returned from the first call to .next(), whereas Unfold runs the closure first.

Also, bizarrely, it runs the closure on next() but doesn't return the value from that call until the next next(). This means that any side effects that the closure may have will occur at a rather unexpected time.

Overall, I don't see the point in having a near-duplicate of Unfold.

@huonw
Copy link
Member

huonw commented Jul 7, 2014

Also, bizarrely, it runs the closure on next() but doesn't return the value from that call until the next next(). This means that any side effects that the closure may have will occur at a rather unexpected time.

This isn't that bizarre: it makes std::iter::Iterate::new(1, |x| x * 2) give 1, 2, 4, 8, ..., while not returning the old value makes giving such a sequence harder. It also matches Haskell's iterate.

@lilyball
Copy link
Contributor

lilyball commented Jul 7, 2014

@huonw That's an artifact of returning the seed, not an artifact of running the closure early. Note that Haskell's iterate does not evaluate the thunk for the next value before you actually need that value (which isn't something special about iterate of course).

The reason that Iterate runs the closure early, of course, is because of how it's implemented internally, because of its need to return the seed. This could be fixed by using a two-state enum.

@lilyball
Copy link
Contributor

lilyball commented Jul 7, 2014

If this behavior is useful, perhaps we should just have a free function fn std::iter::iterate<'a, T>(f: |T|: 'a -> T, seed: T) -> Unfold<'a, T>. This could be done by adding the necessary state to Unfold, or it could even by done purely as a wrapper that puts the necessary state into the state value.

This could also be done as a static method on Unfold if using a free function is considered odd.

@lilyball
Copy link
Contributor

lilyball commented Jul 7, 2014

use std::iter::Unfold;

// FWIW this is a great example of where an `exists` existential type operator
// would be nice, so we could avoid defining the St parameter to Unfold.
// Instead I'm just going to use a non-public type.
type IterateSt<'a,T> = (|T|: 'a -> T, Option<T>, bool);

#[allow(visible_private_types)]
pub fn iterate<'a, T: Clone>(f: |T|: 'a -> T, seed: T) -> Unfold<'a, T, IterateSt<'a, T>> {
    Unfold::new((f, Some(seed), true), |st| {
        let &(ref mut f, ref mut val, ref mut first) = st;
        if *first {
            *first = false;
        } else {
            val.mutate(|x| (*f)(x));
        }
        val.clone()
    })
}

@ghost
Copy link
Author

ghost commented Jul 12, 2014

@kballard Agreed the previous implementation had bizarre characteristics around when the next value is computed.

I took the liberty of using your last implementation, thank you very much. I think a free-standing function is a better fit for this. Arguably, all iterators are just specializations of Unfold. This one's really close but I think the readability win is significant that it still warrants being included in core.

@lilyball
Copy link
Contributor

👍 r=me unless @huonw, @alexcrichton, or @blake2-ppc disagree.

@bluss
Copy link
Member

bluss commented Jul 12, 2014

If you ask me iterate should have its own either struct or type if it is part of std::iter -- it's a module for conveniently reusable items, so the type returned by iterate() needs to be easy ("easy") to use in user's iterator compositions.

@lilyball
Copy link
Contributor

Good point. A pub type I think is sufficient. No need to make a custom struct.

@alexcrichton
Copy link
Member

Looks good to me with pub type.

Implementation by Kevin Ballard.

The function returns an Unfold iterator producing an infinite stream
of results of repeated applications of the function, starting from
the provided seed value.
@ghost
Copy link
Author

ghost commented Jul 13, 2014

@blake2-ppc @kballard @alexcrichton Done.

bors added a commit that referenced this pull request Jul 13, 2014
The new iterator takes a function and produces an infinite stream
of results of repeated applications of the function, starting from
the provided seed value.
@bors bors closed this Jul 13, 2014
@bors bors merged commit ed54162 into rust-lang:master Jul 13, 2014
@ghost ghost deleted the iterate branch July 13, 2014 22:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants