Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When is the Enumerator::Yielder#yield method useful?

The question "Meaning of the word yield" mentions the Enumerator::Yielder#yield method. I haven't used it before, and wonder under what circumstances it would be useful.

Is it mainly useful when you want to create an infinite list of items, such as the Sieve of Eratosthenes, and when you need to use an external iterator?

like image 771
Andrew Grimm Avatar asked Feb 21 '11 22:02

Andrew Grimm


3 Answers

"How to create an infinite enumerable of Times?" talks about constructing and lazy iterators, but my favorite usage is wrapping an existing Enumerable with additional functionality (any enumerable, without needing to know what it really is, whether it's infinite or not etc).

A trivial example would be implementing the each_with_index method (or, more generally, with_index method):

module Enumerable
  def my_with_index
    Enumerator.new do |yielder|
      i = 0
      self.each do |e|
        yielder.yield e, i
        i += 1
      end
    end
  end

  def my_each_with_index
    self.my_with_index.each do |e, i|
      yield e, i
    end
  end
end

[:foo, :bar, :baz].my_each_with_index do |e,i|
  puts "#{i}: #{e}"
end
#=>0: foo
#=>1: bar
#=>2: baz

Extending to something not already implemented in the core library, such as cyclically assigning value from a given array to each enumerable element (say, for coloring table rows):

module Enumerable
  def with_cycle values
    Enumerator.new do |yielder|
      self.each do |e|
        v = values.shift
        yielder.yield e, v
        values.push v
      end
    end
  end
end

p (1..10).with_cycle([:red, :green, :blue]).to_a # works with any Enumerable, such as Range
#=>[[1, :red], [2, :green], [3, :blue], [4, :red], [5, :green], [6, :blue], [7, :red], [8, :green], [9, :blue], [10, :red]]

The whole point is that these methods return an Enumerator, which you then combine with the usual Enumerable methods, such as select, map, inject etc.

like image 116
Mladen Jablanović Avatar answered Nov 14 '22 12:11

Mladen Jablanović


For example you can use it to construct Rack response bodies inline, without creating classes. An Enumerator can also work "outside-in" - you call Enumerator#each which calls next on the enumerator and returns every value in sequence. For example, you can make a Rack response body returning a sequence of numbers:

run ->(env) {
  body = Enumerator.new do |y|
   9.times { |i| y.yield(i.to_s) }
  end
  [200, {'Content-Length' => '9'}, body]
}
like image 1
Julik Avatar answered Nov 14 '22 12:11

Julik


Since Mladen mentioned getting other answers, I thought I would give an example of something I just did earlier today while writing an application that will receive data from multiple physical devices, analyze the data, and connect related data (that we see from multiple devices). This is a long-running application, and if I never threw away data (say, at least a day old with no updates), then it would grow infinitely large.

In the past, I would have done something like this:

delete_old_stuff if rand(300) == 0

and accomplish this using random numbers. However, this is not purely deterministic. I know that it will run approximately once every 300 evaluations (i.e. seconds), but it won't be exactly once every 300 times.

What I wrote up earlier looks like this:

counter = Enumerator.new do |y|
  a = (0..300)
  loop do
    a.each do |b|
      y.yield b
    end
    delete_old_stuff
  end
end

and I can replace delete_old_stuff if rand(300) == 0 with counter.next

Now, I'm sure there is a more efficient or pre-made way of doing this, but being sparked to play with Enumerator::Yielder#yield by your question and the linked question, this is what I came up with.

like image 1
Paul Hoffer Avatar answered Nov 14 '22 10:11

Paul Hoffer