Перечислитель yielder.yield VS Proc.yield

Недавно я начал читать книгу "Programming Ruby 1.9&2.0". Он показывает трюк для явного перечислителя

triangular_numbers = Enumerator.new do |yielder|
number = 0
count = 1
    loop do
        number += count
        count += 1
        yielder.yield number
    end
end
5.times { print triangular_numbers.next, " " }
puts

Интересно, почему этот yielder.yield временно покидает цикл, а также возвращает значение number до тех пор, пока не будет создан следующий объект перечислителя. Это выглядит иначе, чем в обычном случае, когда yield находится внутри блока цикла. Я проверяю APIdock и обнаруживаю, что исходный код Proc.yield() такой же, как у Proc.call(). Для объекта Yielder в классе Enumerator объект Yielder переопределяет yield(). Но почему yielder.yield временно покидает блок loop?

Ссылка: APIdock Yielder yield(), Ruby MRI rb_proc_call


person James Yang    schedule 18.09.2013    source источник
comment
См. здесь: stackoverflow.com/questions/993026/ Если у вас есть какие-либо вопросы, дайте мне знать.   -  person 7stud    schedule 18.09.2013
comment
Я думаю, вы можете быть смущены Enumerators. Вы говорите, пока не [будет] создан следующий объект перечислителя, но здесь есть только один перечислитель: triangular_numbers. Перечислитель действует как замыкание, поэтому он может запоминать значение number между вызовами.   -  person Max    schedule 18.09.2013


Ответы (2)


Вы путаете оператор yield Ruby с методом yield Enumerator::Yielder и методом yield Proc. Они могут быть написаны одинаково, но они совершенно разные.

Заявление

Оператор yield не имеет получателя. Внутри метода это означает «Запустить блок прямо сейчас». Ошибка возникает, если блок не прикреплен. Это не всегда аргумент, потому что иногда вы просто хотите запустить блок.

def foo
  yield :bar
end
foo # LocalJumpError
foo { |x| puts x } # bar

Перечислитель:: Выход

Для yielder yield почти всегда дается аргумент. Это потому, что это означает то же самое, что и <<, что означает «В следующий раз, когда кто-то вызовет next на меня, дайте им это значение».

Enumerator.new { |yielder| yielder.yield 3 }.next # 3
Enumerator.new { |yielder| yielder << 3 }.next # same thing

Я думаю, что лучше использовать <<, чтобы избежать путаницы с оператором yield.

Проц

Procs и lambdas — это в основном функции. yield здесь означает то же самое, что и call, что означает «Просто вызвать функцию». Вы можете дать ему аргумент или нет, в зависимости от того, как был определен процесс. Здесь нет ничего необычного.

proc { |x| puts x }.yield(:bar) # bar
proc { |x| puts x }.call(:bar) # same thing as previous line

Я думаю, что лучше использовать call, чтобы избежать путаницы с оператором yield.

person Max    schedule 18.09.2013
comment
После прочтения этого у меня возник соблазн использовать yield вместо вызова, потому что это звучит приятнее, но будьте осторожны, объекты методов не реагируют на yield. - person ; 26.07.2016

Я также наткнулся на этот пример в книге. После некоторого размышления о том, как этот пример может работать, и просмотрев документацию по Ruby, я нашел класс Fiber, который, как мне кажется, используется Enumerator за кулисами:

http://www.ruby-doc.org/core-2.0/Fiber.html

Концепция Fiber реализует «облегченный кооперативный параллелизм», что довольно интересно, не так сложно для понимания и, что более важно, отличается от других «продуктов» для вызова блоков или работы с элементами управления потоками.

Я думаю, что Enumerator имеет объект Fiber, внутри которого он переходит к блоку. Тогда похоже, что каждый раз, когда вы вызываете «далее» в своем перечислителе, он вызывает «возобновить» для объекта Fiber, чтобы позволить ему вычислить следующее число, и когда блок вызывает «выход» для волокна, управление возвращается к « метод "следующий". И так далее.

Вот мой вариант возможной реализации Enumerator (разумеется, только та часть, которая обсуждается в примере книги):

class MyExplicitEnumerator

  def initialize (&block)
    @yielder = Fiber.new { block.call Fiber }
  end

  def next
    @yielder.resume
  end

end

e = MyExplicitEnumerator.new do |yielder| 
    number = 1
    loop do
      yielder.yield number
      number += 1
    end
  end

p e.next
p e.next

# output
# 1
# 2
person Oleksandr Brezitskyy    schedule 13.06.2014