Помогите разобраться в доходах и счетчиках в Ruby - PullRequest
4 голосов
/ 14 июня 2009

Буду признателен, если кто-нибудь поможет мне понять разницу между использованием Yielder в Enumerator и простым вызовом yield в Enumerator.

«Обоснованный рубист» предполагает, что никто не «уступает из блока», но не объясняет точно, что происходит.

Спасибо

Ответы [ 3 ]

5 голосов
/ 12 августа 2013

Может помочь, если вы сначала поймете, как работает 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.

Некоторые важные вещи, на которые стоит обратить внимание:

  1. выход называется внутри метода

  2. Когда вы вызываете метод, вы можете передать блок методу

  3. 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, поэтому легче обнаружить в коде и легко определить, где выполнение останавливается.

3 голосов
/ 15 июня 2009

Ну, если я что-то упустил, метод с yield просто не работает. Попробуйте:

e = Enumerator.new do |y|
  y << 1
  y << 2
  y << 3
end

f = Enumerator.new do
  yield 1
  yield 2
  yield 3
end

e.each { |x| puts x }
f.each { |x| puts x }

Который производит это:

telemachus ~ $ ruby yield.rb 
1
2
3
yield.rb:13:in `block in <main>': no block given (yield) (LocalJumpError)
        from yield.rb:19:in `each'
        from yield.rb:19:in `each'
        from yield.rb:19:in `<main>

Когда он говорит (стр. 304): «Вы не делаете это», он не имеет в виду «это не лучший способ сделать это». Он имеет в виду «это не сработает».

Редактировать: Однако вы можете явно вызвать yield так:

e = Enumerator.new do |y|
  y.yield 1
  y.yield 2
  y.yield 3
end

Если вы находите высказывание yield более явным или более ясным, чем <<, сделайте это так.

Второе редактирование: Глядя на оригинальный пост Дэвида и обновленный ответ Йорга, я думаю, что изначально возникла путаница по этому вопросу. Йорг подумал, что Дэвид спрашивает о разнице между Enumerator::Yielder#yield и Enumerator::Yielder::<<, но Дэвид не был уверен, что означает Хорошо обоснованный рубист , когда он говорит "не пиши yield 1 и т. Д." Мой ответ относится к вопросу о Обоснованном Рубиисте . (Когда я просмотрел эту ветку сегодня, мой ответ выглядел странно в свете других обновлений.)

2 голосов
/ 15 июня 2009

Метод Enumerator::Yielder#yield и метод Enumerator::Yielder::<< равны точно так же . На самом деле это псевдонимы.

Итак, какой из этих двух вы используете, это 100% личное предпочтение, как Enumerable#collect и Enumerable#map или Enumerable#inject и Enumerable#reduce.

...