HI --

On Mon, 3 Nov 2008, Brian Candler wrote:

> I've been having a play with Enumerators in ruby 1.9, in particular
> adding 'lazy' versions of those methods in Enumerable which normally
> return arrays. Here's a proof-of-concept implementation:
>
> 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
>
>  def ltake(n)
>    Enumerator.new do |y|
>      count = 0
>      each do |e|
>        break if n <= count
>        y << e
>        count += 1
>      end
>    end
>  end
>
>  def lskip(n)
>    Enumerator.new do |y|
>      count = 0
>      each do |e|
>        y << e unless count <= n
>        count += 1
>      end
>    end
>  end
> end
>
> if __FILE__ == $0
>  big = (1..1_000_000_000_000)
>  big.lselect { |i| i % 2 == 1 }.lmap { |i| i + 100 }.
>      lskip(5).ltake(10).each { |i| puts i }
> end
>
> So instead of generating an array containing all processed elements,
> they return an Enumerator which processes the elements on demand. As a
> result, you can easily chain them together, as shown in the example; you
> can handle large sequences without creating large intermediate arrays;
> and also handle infinite lists.
>
> Ruby hides the internals of this very nicely, I presume using Fibers to
> maintain the state inside each Enumerator.
>
> My first question is, does ruby 1.9 have methods like this already, and
> I've just overlooked them? If so, where should I be looking?

It did have something like this, but not any more. I'm not sure they
were identical to what you've written, but the basic idea of an
enumerator that bundled itself with a block did exist in 1.9 for a
while.

> If not, then is there value in adding something like this to the
> language? For example,

I think so, absolutely. I'm still struggling with trying to believe
that enumerators are more than marginally useful without the ability
to travel with knowledge of a block and still be lazy.

> 1. Put this stuff into an external library, Facets-like, with new
> methods in Enumerable as above (the method names given are just
> examples, there are probably better ones)
>
> 2. Redefine Enumerator#map, Enumerator#select etc, so that they return
> Enumerators instead of arrays. It seems reasonable to me that once you
> have an Enumerator at the base of the chain, you will likely want to
> keep chaining them together. You can always collapse the result to a
> real array using 'to_a'.

I'd strongly go for #1. #2 would require not only massive code
overhaul, but change of habits, and lots of to_a is unsightly (and you
wouldn't necessarily want to have to hard-code the class of what you
want back, anyway [i.e., the 'a' in 'to_a']).


David

-- 
Rails training from David A. Black and Ruby Power and Light:
   Intro to Ruby on Rails  January 12-15   Fort Lauderdale, FL
   Advancing with Rails    January 19-22   Fort Lauderdale, FL *
   * Co-taught with Patrick Ewing!
See http://www.rubypal.com for details and updates!