_If you wish to report errors or suggest improvements for this FAQ,
please go to our
[GitHub repository](https://github.com/ruby/www.ruby-lang.org/)
and open an issue or pull request._


## Итераторы

### Что такое итератор?

Итератор — это метод, который принимает блок или объект `Proc`. В исходном файле блок помещается сразу после вызова метода. Итераторы используются для создания пользовательских структур управления — особенно циклов.

Давайте рассмотрим пример, чтобы увидеть, как это работает. Итераторы часто используются для повторения одного и того же действия для каждого элемента коллекции, например так:

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

Результат:

~~~
1
2
3
~~~

Методу `each` массива `data` передается блок `do ... end`, и он выполняет его многократно. При каждом вызове блоку передаются последовательные элементы массива.

Вы можете определять блоки с помощью `{ ... }` вместо `do ... end`.

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

Результат:

~~~
1
2
3
~~~

Этот код имеет тот же смысл, что и в предыдущем примере. Однако в некоторых случаях из-за приоритетов операций `do ... end` и `{ ... }` ведут себя по-разному.

~~~
foobar a, b do ... end  # foobar является итератором.
foobar a, b { ... }     # b является итератором.
~~~

Это происходит потому, что `{ ... }` связывается с предшествующим выражением сильнее, чем блок `do ... end`. Первый пример эквивалентен `foobar(a, b) do ... end`, а второй — `foobar(a, b { ... })`.

### Как мне передать блок итератору?

Вы просто помещаете блок после вызова итератора. Вы также можете передать объект `Proc`, добавив `&` перед именем переменной или константы, которая ссылается на `Proc`.

### Как блок используется в итераторе?

<div class="not-prose bg-ruby-100 dark:bg-ruby-800 rounded-lg p-8 my-6">
  <div class="flex items-start gap-3">
    <svg class="w-6 h-6 text-ruby-700 dark:text-ruby-200 flex-shrink-0 mt-0.5" fill="currentColor" viewBox="0 0 20 20">
      <path fill-rule="evenodd" d="M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.17 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495zM10 5a.75.75 0 01.75.75v3.5a.75.75 0 01-1.5 0v-3.5A.75.75 0 0110 5zm0 9a1 1 0 100-2 1 1 0 000 2z" clip-rule="evenodd"/>
    </svg>
    <p class="text-sm text-ruby-700 dark:text-ruby-200 italic">
      This section or parts of it might be out-dated or in need of confirmation.
    </p>
  </div>
</div>


Существует три способа выполнить блок из метода итератора: (1) управляющая структура `yield`; (2) вызов аргумента `Proc` (созданного из блока) с помощью `call`; и (3) использование `Proc.new`, за которым следует вызов.

Инструкция `yield` вызывает блок, опционально передавая ему один или несколько аргументов.

~~~
def my_iterator
  yield 1, 2
end

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

Результат:

~~~
1
2
~~~

Если в определении метода есть аргумент блока (перед последним формальным параметром стоит амперсанд (`&`)), он получит прикрепленный блок, преобразованный в объект `Proc`. Его можно вызвать с помощью `prc.call(args)`.

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

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

Результат:

~~~
1
2
~~~

`Proc.new` (или эквивалентные вызовы `proc` или `lambda`) при использовании в определении итератора берет блок, переданный методу в качестве аргумента, и генерирует из него объект процедуры. (`proc` и `lambda` фактически являются синонимами).

_[Требуется обновление: `lambda` ведет себя немного иначе и вызывает предупреждение `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 }
~~~

Результат:

~~~
3
4
5
6
7
8
~~~

Возможно, это удивительно, но `Proc.new` и компания ни в коем случае не «потребляют» блок, прикрепленный к методу — каждый вызов `Proc.new` генерирует новый объект процедуры из того же самого блока.

Вы можете узнать, связан ли с методом блок, вызвав `block_given?`.

### Что делает `Proc.new` без блока?

`Proc.new` без блока не может сгенерировать объект процедуры, и возникает ошибка. Однако в определении метода `Proc.new` без блока подразумевает наличие блока в момент вызова метода, поэтому ошибки не возникнет.

### Как я могу запускать итераторы параллельно?

Вот адаптация решения от Matz из [\[ruby-talk:5252\]][ruby-talk:5252], которое использует потоки:

~~~
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 это [1, 4], затем [2, 5], затем [3, 6]
end
~~~

[ruby-talk:5252]: https://blade.ruby-lang.org/ruby-talk/5252
