Search code examples
rubysequenceenumerator

Enumerator#each Restarts Sequence


I'm surprised that Enumerator#each doesn't start off at the current position in the sequence.

o = Object.new

def o.each
  yield 1
  yield 2
  yield 3
end

e = o.to_enum
puts e.next
puts e.next
e.each{|x| puts x}
# I expect to see 1,2,3 but I see 1,2,1,2,3
# apparently Enumerator's each (inherited from Enumerable) restarts the sequence!

Am I doin' it wrong? Is there a way to maybe construct another Enumerator (from e) that will have the expected each behavior?


Solution

  • You're not doing it wrong, that's just not the semantics defined for Enumerator#each. You could make a derivative enumerator that only iterates from current position to end:

    class Enumerator
      def enum_the_rest
        Enumerator.new { |y| loop { y << self.next } }
      end
    end
    
    o = Object.new
    def o.each
      yield 1
      yield 2
      yield 3
    end
    
    e = o.to_enum
    => #<Enumerator: ...>
    e.next
    => 1
    e2 = e.enum_the_rest
    => #<Enumerator: ...>
    e2.each { |x| puts x }
    => 2
    => 3
    

    And, BTW, each doesn't restart the sequence, it just always runs over the entire span. Your enumerator still knows where it is in relation to the next next call.

    e3 = o.to_enum
    e3.next
    => 1
    e3.next
    => 2
    e3.map(&:to_s)
    => ["1", "2", "3"]
    e3.next
    => 3