Yuh-Ruey C. wrote:
Yes, this is exactly what I’m looking for - the ability to chain
iterators like this without creating intermediate arrays, hopefully
using less memory.
I’ve researched this a bit more, and it seems that ruby 1.9 does
support this model in a very smart way. Unfortunately the built-in
Enumerable methods like #map, #select, #reject don’t, but these are easy
enough to synthesise. I started a new thread on that topic at
http://www.ruby-forum.com/topic/169807
Anyway, if you want the following pattern
generator → filter → filter … → consumer
you implement it as follows.
(1) The generator is any object which implements ‘each’ and yields the
members of the (possibly infinite) collection.
class Fib
include Enumerable
def initialize(a=1,b=1)
@a, @b = a, b
end
def each
a, b = @a, @b
yield a
while true
yield b
a, b = b, a+b
end
end
end
(by including Enumerable, you gain a whole load of useful methods which
in turn call your ‘each’ method)
(2) The consumer is an object which calls ‘each’ with a block that does
something with the data yielded to it:
obj.each { |i| puts i }
So far, all so ruby-1.8.
(3) The clever bit is the filter, and this only works in ruby-1.9. You
create an Enumerator object with a block which processes the data.
Enumerator.new do |y|
obj.each do |thing| # the filter INPUT
…
y << otherthing # the filter OUTPUT
end
end
The really clever bit is the “y << otherthing” line. By cunning use of
Fibers (I believe), execution of the filter stops at this point,
allowing the next filter along the chain to make use of the value as its
input. Once all the filters along the chain have finished using this
value, this filter carries on into the next iteration of its ‘each’
loop, to take the next input from upstream.
So here’s a complete example with an infinite source, two filters, and a
consumer. Notice that only the consumer decides when to terminate;
before this point, everything is a potentially infinite series.
module Enumerable
def lmap(&blk)
Enumerator.new do |y|
each do |e|
y << blk[e]
end
end
end
def lselect(&blk)
Enumerator.new do |y|
each do |e|
y << e if blk[e]
end
end
end
end
class Fib
include Enumerable
def initialize(a=1,b=1)
@a, @b = a, b
end
def each
a, b = @a, @b
yield a
while true
yield b
a, b = b, a+b
end
end
end
Fib.new.lselect { |i| i % 2 == 0 }.lmap { |i| “<#{i}>” }.
each_with_index { |i,cnt| puts i; break if cnt >= 20 }
$ ruby19 fib.rb
<2>
<8>
<34>
<144>
<610>
<2584>
<10946>
<46368>
<196418>
<832040>
<3524578>
<14930352>
<63245986>
<267914296>
<1134903170>
<4807526976>
<20365011074>
<86267571272>
<365435296162>
<1548008755920>
<6557470319842>