Content | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11

Official Ruby FAQ

If you wish to report errors or suggest improvements for this FAQ, please go to our GitHub repository and open an issue or pull request.

Iterators

What is an iterator?

An iterator is a method which accepts a block or a Proc object. In the source file, the block is placed immediately after the invocation of the method. Iterators are used to produce user-defined control structures—especially loops.

Let’s look at an example to see how this works. Iterators are often used to repeat the same action on each element of a collection, like this:

data = [1, 2, 3]
data.each do |i|
  puts i
end

Produces:

1
2
3

The each method of the array data is passed the do ... end block, and executes it repeatedly. On each call, the block is passed successive elements of the array.

You can define blocks with { ... } in place of do ... end.

data = [1, 2, 3]
data.each { |i|
  puts i
}

Produces:

1
2
3

This code has the same meaning as the last example. However, in some cases, precedence issues cause do ... end and { ... } to act differently.

foobar a, b do ... end  # foobar is the iterator.
foobar a, b { ... }     # b is the iterator.

This is because { ... } binds more tightly to the preceding expression than does a do ... end block. The first example is equivalent to foobar(a, b) do ... end, while the second is foobar(a, b { ... }).

How can I pass a block to an iterator?

You simply place the block after the iterator call. You can also pass a Proc object by prepending & to the variable or constant name that refers to the Proc.

How is a block used in an iterator?

This section or parts of it might be out-dated or in need of confirmation.

There are three ways to execute a block from an iterator method: (1) the yield control structure; (2) calling a Proc argument (made from a block) with call; and (3) using Proc.new followed by a call.

The yield statement calls the block, optionally passing it one or more arguments.

def my_iterator
  yield 1, 2
end

my_iterator {|a, b| puts a, b }

Produces:

1
2

If a method definition has a block argument (the last formal parameter has an ampersand (&) prepended), it will receive the attached block, converted to a Proc object. This may be called using prc.call(args).

def my_iterator(&b)
  b.call(1, 2)
end

my_iterator {|a, b| puts a, b }

Produces:

1
2

Proc.new (or the equivalent proc or lambda calls), when used in an iterator definition, takes the block which is given to the method as its argument and generates a procedure object from it. (proc and lambda are effectively synonyms.)

[Update needed: lambda behaves in a slightly different way and produces a warning tried to create Proc object without a block.]

def my_iterator
  Proc.new.call(3, 4)
  proc.call(5, 6)
  lambda.call(7, 8)
end

my_iterator {|a, b| puts a, b }

Produces:

3
4
5
6
7
8

Perhaps surprisingly, Proc.new and friends do not in any sense consume the block attached to the method—each call to Proc.new generates a new procedure object out of the same block.

You can tell if there is a block associated with a method by calling block_given?.

What does Proc.new without a block do?

Proc.new without a block cannot generate a procedure object and an error occurs. In a method definition, however, Proc.new without a block implies the existence of a block at the time the method is called, and so no error will occur.

How can I run iterators in parallel?

Here an adoption of a solution by Matz, in [ruby-talk:5252], that uses threads:

require "thread"

def combine(*iterators)
  queues = []
  threads = []

  iterators.each do |it|
    queue = SizedQueue.new(1)
    th = Thread.new(it, queue) do |i, q|
           send(i) {|x| q << x }
         end
    queues  << queue
    threads << th
  end

  loop do
    ary = []
    queues.each {|q| ary << q.pop }
    yield ary

    iterators.size.times do |i|
      return if !threads[i].status && queues[i].empty?
    end
  end
end

def it1
  yield 1; yield 2; yield 3
end

def it2
  yield 4; yield 5; yield 6
end

combine(:it1, :it2) do |x|
  # x is [1, 4], then [2, 5], then [3, 6]
end