Skip to content
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 missing Iterator::SingletonProc#rewind #3795

Conversation

makenowjust
Copy link
Contributor

No description provided.

@asterite
Copy link
Member

rewind shouldn't be available for Iterator.of (as a side note, maybe Iterator.of should have another name, it's a bit confusing).

For example:

a = b = 1
fib = Iterator.of do
  a, b = b, a
  a = a + b
  b
end

p fib.next # => 1
p fib.next # => 2
p fib.next # => 3
p fib.next # => 5
p fib.next # => 8

fib.rewind # ???

We can't rewind because there's no way to know the initial state of the iterator, which forms a closure. So rewind is generally not available in this iterator.

@makenowjust
Copy link
Contributor Author

@asterite Yes, I know all you said. Though I think Iterator#rewind is implemented by Iterator::SingletonProc, just error message says: abstract def Iterator(T)#rewind() must be implemented by Iterator::SingletonProc(...). Almost all people believe all Iterator implements rewind.

At first, I thought we should raise a runtime exception when calling Iterator::SingletonProc#rewind. But for example, the iterator returned by IO#each_line for STDIN cannot rewind, but it just works:

lines = STDIN.each_line
p lines.first(2).to_a
lines.rewind.each do |line|
  p line
end

So, I decide to return itself by SingletonProc#rewind without an exception. (Of course, I know STDIN.each_line's iterator rewind calls IO#rewind, but it is no effect for STDIN in most case.)

@asterite
Copy link
Member

I think STDIN.rewind is a border case, it should raise but libc lets you do that and doesn't tell you there's something wrong. I don't think we should take this as an example.

Maybe Iterator#rewind should raise if it can be implemented for a particular type of iterator, but I don't know yet.

@makenowjust
Copy link
Contributor Author

@asterite

Maybe Iterator#rewind should raise if it can be implemented for a particular type of iterator, but I don't know yet.

I think it is better than now 😄

@makenowjust
Copy link
Contributor Author

@asterite In fact, rewind is called in many cases when next returns Iterator::Stop, but Iterator::SingletonProc#next returns no Iterator::Stop (if the block giving to Iterator.of don't return Iterator::Stop). So, it is no problem.

@straight-shoota
Copy link
Member

#codetriage
This was left open with I believe two possible courses:

  • Iterators which don't support rewinding should just not complain but just keep going => merge this PR
  • Iterators which don't support rewinding should raise => close this PR and open a new one which implements this behaviour.

As I understand it, option 2 was considered more favourable and I'd support that move. It's better to fail loudly instead of continuing silently without notice.
But I think it could even be better to remove rewind method from Iterator and only implement it in subclasses that support rewinding. Rewinding is not a constituting feature of an iterator, there are plenty of iterators without rewind support. So the best thing would be to not even allow to compile a program where an iterator would be rewinded that can't.

@asterite
Copy link
Member

There's a third possibility. rewind exists because of a single reason: to implement cycle.

In other languages an Iterator can only go forward. This is true in Java and C#, for example. Probably in Rust too, not sure. Specially if those "iterators" are created from code (like in the case of C#'s yield return).

I added rewind to Iterator because most of the time it can be implemented, and also because I thought it was needed to implement cycle, to go back to the beginning of the iteration.

I was surprised to see this code working fine in Ruby:

p STDIN.each_line.first(3).cycle(10).to_a

How can that work??

Well, turns out that their cycle just remembers the values in an internal array and then traverses that. From the docs:

#cycle saves elements in an internal array so changes to enum after the first pass have no effect.

I think that's acceptable. When you cycle over elements they are usually not that many. And if they are, well, at least they will not be infinite (because what would be the point of cycle then?). So yes, it occupies a bit more memory but it works better and it could actually be faster (just advance an index).

I just tried it out and it works really well (including that snippet from Ruby). I don't think rewinding iterators is that common, or actually needed.

Another advantage is that it simplifies implementing an Iterator: just define next.

What do you think? Should I create a separate issue/PR to discuss this?

@asterite
Copy link
Member

Closed because in #7440 we removed Iterator#rewind.

@straight-shoota
Copy link
Member

straight-shoota commented Feb 26, 2019

@asterite Why add this to the milestone? Also, is this really a bug?

@asterite asterite removed this from the 0.28.0 milestone Feb 26, 2019
@asterite
Copy link
Member

oops

@makenowjust makenowjust deleted the fix/iterator/singleton-proc branch February 27, 2019 04:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants