Может помочь, если вы сначала поймете, как работает yield. Вот пример:
def do_stuff
if block_given?
yield 5
else
5
end
end
result = do_stuff {|x| x * 3 }
puts result
--output:--
15
В вызове метода do_stuff:
do_stuff {|x| x * 3 }
.. блок похож на функцию и передается методу do_stuff. Внутри do_stuff, yield вызывает функцию и передает указанные аргументы - в данном случае 5.
Некоторые важные вещи, на которые стоит обратить внимание:
выход называется внутри метода
Когда вы вызываете метод, вы можете передать блок методу
yield используется для вызова блока.
Хорошо, теперь давайте посмотрим на ваш комментарий:
Правда ли, что
e = Enumerator.new do |y|
y << 1
y << 2
y << 3
end
точно так же, как
e = Enumerator.new do #I think you forgot to write .new here
yield 1
yield 2
yield 3
end
Во втором примере нигде нет определения метода, поэтому вы не можете вызвать yield. Ошибка! Поэтому два примера не совпадают.
Однако вы можете сделать это:
def do_stuff
e = Enumerator.new do
yield 1
yield 2
yield 3
end
end
my_enum = do_stuff {|x| puts x*3}
my_enum.next
--output:--
3
6
9
1.rb:12:in `next': iteration reached an end (StopIteration)
from 1.rb:12:in `<main>'
Но это забавный перечислитель, потому что он не выдает никаких значений - он просто выполняет некоторый код (который выводит некоторые выходные данные), а затем завершается. Этот перечислитель почти эквивалентен:
def do_stuff
e = Enumerator.new do
end
end
my_enum = do_stuff
my_enum.next
--output:--
1.rb:7:in `next': iteration reached an end (StopIteration)
from 1.rb:7:in `<main>'
Когда перечислитель не может создать значение, он вызывает исключение StopIteration. Таким образом, в обоих случаях перечислитель не может создать значение.
Но мне все еще не ясно, что делает "урожай". Это выглядит
как он собирает все рассчитанные значения, чтобы он мог
срыгивайте их позже, когда вы используете перечислитель. Если это
случае, то кажется, что это будет практичным только для "малых"
последовательности .... вы не хотели бы сделать перечислитель, который хранит 50
миллион предметов прочь.
Нет. Фактически, вы можете создать перечислитель, который выдает бесконечное число значений. Вот пример:
e = Enumerator.new do |y|
val = 1
while true
y << val
val += 1
end
end
puts e.next
puts e.next
puts e.next
--output:--
1
2
3
Добавление некоторых сообщений отладки должно оказаться проницательным:
e = Enumerator.new do |y|
val = 1
while true
puts "in while loop"
y << val
val += 1
end
end
puts e.next
--output:--
in while loop
1
Обратите внимание, что сообщение печатается только один раз. Итак, что-то происходит, что не очевидно:
e = Enumerator.new do |y|
val = 1
while true
puts "in while loop"
y << val
puts "just executed y << val"
val += 1
end
end
puts e.next
--output:--
in while loop
1
Поскольку сообщение «только что выполнено y << val» не отображается в выводе, это означает, что выполнение должно быть остановлено в строке <code>y << val. Поэтому перечислитель не вращал цикл while непрерывно и вставлял все значения в y - даже если синтаксис точно такой же, как и при вставке значений в массив: arr << val
.
Что действительно означает y << val
: когда вызывается e.next (), выведите это значение, затем продолжите выполнение на следующей строке. Если вы добавите еще один e.next в предыдущий пример, вы увидите следующий дополнительный вывод:
just executed y << val
in while loop
2
То, что происходит, заключается в том, что выполнение всегда останавливается, когда в коде встречается y << val
. Затем вызов e.next возвращает значение справа, затем выполнение продолжается на следующей строке.
Вероятно, имело бы больше смысла, если бы ruby использовал синтаксис для выражения yielder следующим образом:
y >> val
И мы могли бы интерпретировать это как значение: остановите выполнение здесь, затем, когда e.next называется, производят val.
Дэвид Блэк рекомендует не использовать синтаксис y.yield val
, что эквивалентно y << val
, чтобы читатели не думали, что он работает аналогично оператору yield. y.yield val
следует интерпретировать как: «остановите выполнение здесь, и когда next вызывается, произведите val, затем продолжите выполнение на следующей строке. Лично я считаю, что синтаксис y << val
выделяется больше, чем y.yield val
, поэтому легче обнаружить в коде и легко определить, где выполнение останавливается.