New Generator

G

Greg Fitzgerald

Ruby gurus,

There is a side-effect in the current Generator implementation. When
iterating to an element, it caches the element *after* the one it
returns each iteration. Meaning that when you first construct the
generator, it caches the first element. When you call next() it will
set aside the cached value to return, and then iterate to the second
element, make it the new cached value, and return the first. You will
only notice this side-effect in code where you don't iterate through
every element in the generator, such as a 'find' operation. The
side-effect becomes problematic if either it takes significant time to
retrieve the next element (crossing a network) or when iterating is a
destructive operation (pulling data off a stream).

Below is the code that demonstrates the issue and provides a new
implementation of Generator that corrects it. Although I haven't done
tons of testing on it, I believe the API behaves in all ways like the
original.

Do you all think this may be something worth correcting in the next
release of Ruby? If so, is there a document around somewhere for how
to go about submitting a patch?


require 'generator'

def test_generator()
timeStart = Time.now
puts "start constructing at #{Time.now - timeStart}"
gen = Generator.new do |g|
for num in [1,2,3,4,5]
sleep(3)
g.yield(num)
end
end

puts "finished constructing at #{Time.now - timeStart}"

puts "getting first element at #{Time.now - timeStart}"
for num in gen
puts "#{num} at #{Time.now - timeStart}"
break if num == 3
puts "getting next element at #{Time.now - timeStart}"
end
puts "done at #{Time.now - timeStart}"

puts "\n\n"
end

puts "Ruby's Generator"
test_generator()

class Generator
def initialize(&closure)
@closure = closure
return rewind()
end

def each()
rewind()

while self.next?()
yield self.next()
end
return self
end

def rewind()
if @position != 0
@currentElement = callcc do |cc|
@generatorEntryPoint = cc
@closure.call(self)
@closureEntryPoint = nil
@generatorEntryPoint.call()
end

@nextElement = @currentElement
@nextElementCached = true
@position = 0
end
return self
end

def pos()
return @position
end

def current()
return @currentElement
end

def next?()
if @nextElementCached == false
begin
@nextElement = retrieve_next()
@nextElementCached = true
rescue EOFError
end
end
return @nextElementCached
end

def end?()
return !self.next?()
end

def next()
@currentElement = @nextElementCached ? @nextElement : retrieve_next()
@position += 1
@nextElementCached = false
return @currentElement
end

def retrieve_next()
retval = callcc do |cc|
@generatorEntryPoint = cc
@closureEntryPoint.call()
end
raise EOFError, 'no more elements available' if @closureEntryPoint == nil
return retval
end

def yield(obj)
return callcc do |cc|
@closureEntryPoint = cc
@generatorEntryPoint.call(obj)
end
end
end

puts "Corrected generator"
test_generator()
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,774
Messages
2,569,599
Members
45,172
Latest member
NFTPRrAgenncy
Top