Зміст | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11

Офіційний FAQ Ruby

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.

Ітератори

Що таке ітератор?

Ітератор — це метод, який приймає блок або об’єкт 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 is the iterator.
foobar a, b { ... }     # b is the iterator.

Це тому, що { ... } прив’язується до попереднього виразу сильніше, ніж блок do ... end. Перший приклад еквівалентний foobar(a, b) do ... end, тоді як другий — foobar(a, b { ... }).

Як передати блок до ітератора?

Просто розмістіть блок після виклику ітератора. Ви також можете передати об’єкт Proc, додавши & перед іменем змінної або константи, що посилається на Proc.

Як використовується блок в ітераторі?

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

Є три способи виконати блок з методу-ітератора: (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], що використовує потоки:

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