Помогите разобраться в yield и enumerators в Ruby

StackOverflow https://stackoverflow.com/questions/993026

  •  13-09-2019
  •  | 
  •  

Вопрос

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

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

Спасибо

Это было полезно?

Решение

Тот Самый Enumerator::Yielder#yield способ и Enumerator::Yielder::<< методом являются точно такой же.На самом деле, это псевдонимы.

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

Другие советы

Это могло бы помочь, если бы вы сначала поняли, как работает 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" не отображается в выходных данных, это означает, что выполнение, должно быть, было остановлено в строке y << val.Следовательно, перечислитель не выполнял непрерывный цикл while и не вставлял все значения в y, хотя синтаксис точно такой же, как при вводе значений в массив: arr << val.

Что y << val на самом деле означает, что есть:когда вызывается функция e.next(), выдающая это значение, затем продолжайте выполнение в следующей строке.Если вы добавите еще одно e.рядом с предыдущим примером, вы увидите этот дополнительный вывод:

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

Ну, если я чего-то не упустил, метод с 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) «ты не «сделай это», он не имеет в виду «это не лучший способ сделать это». Он имеет в виду «это не сработает».

Редактировать:Однако вы можете вызвать выход явно следующим образом:

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

Если вы обнаружите, что говорят yield более явный или ясный, чем <<, тогда делай так.

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top