Как работают счетчики в Ruby 1.9.1?

Этот вопрос не о том, как использовать перечислители в Ruby 1.9.1, а о том, как они работают. Вот некоторый код:

class Bunk
  def initialize
    @h = [*1..100]
  end

  def each
    if !block_given?
      enum_for(:each)
    else
      0.upto(@h.length) { |i|
        yield @h[i]
      }
    end
  end
end

В приведенном выше коде я могу использовать e = Bunk.new.each, а потом e.next, e.next получить каждый последующий элемент, но как именно он приостанавливает выполнение и затем возобновляет работу в нужном месте?

Я знаю, что если доходность в 0.upto заменяется на Fiber.yield тогда это легко понять, но здесь дело не в этом. Это старый добрый yield, Итак, как это работает?

Я посмотрел на enumerator.c, но мне все еще непонятно. Может быть, кто-то мог бы предоставить реализацию в Ruby, используя волокна, а не перечислители на основе продолжения стиля 1.8.6, что бы все прояснило?

4 ответа

Решение

Вот простой перечислитель ruby, который использует Fibers и должен вести себя как оригинал:

class MyEnumerator
  include Enumerable

  def initialize(obj, iterator_method)
    @f = Fiber.new do
      obj.send(iterator_method) do |*args|
        Fiber.yield(*args)
      end
      raise StopIteration
    end
  end

  def next
    @f.resume
  end

  def each
    loop do
      yield self.next
    end
  rescue StopIteration
    self
  end
end

И прежде, чем кто-то будет жаловаться на исключения в качестве управления потоком: настоящий Enumerator также вызывает StopItered в конце, поэтому я просто эмулировал исходное поведение.

Использование:

>> enum = MyEnumerator.new([1,2,3,4], :each_with_index)
=> #<MyEnumerator:0x9d184f0 @f=#<Fiber:0x9d184dc>
>> enum.next
=> [1, 0]
>> enum.next
=> [2, 1]
>> enum.to_a
=> [[3, 2], [4, 3]]

На самом деле в вашем e = Bunk.new.each предложение else изначально не выполняется. Вместо этого выполняется условие if! Block_given, которое возвращает объект перечислителя. Объект перечислителя действительно сохраняет объект волокна внутри. (По крайней мере, так это выглядит в enumerator.c)

Когда вы вызываете e.each, он вызывает метод для перечислителя, который использует внутреннее волокно для отслеживания его контекста выполнения. Этот метод вызывает метод Bunk.each, используя контекст выполнения волокон. Вызов Bunk.each здесь выполняет условие else и возвращает значение.

Я не знаю, как реализован yield или как волокно отслеживает контекст выполнения. Я не смотрел на этот код. Почти все перечислители и волоконные магики реализованы на C.

Вы действительно спрашиваете, как реализованы волокна и урожайность? Какой уровень детализации вы ищете?

Если я с базы, поправьте меня.

Я думаю, что это будет более точным. Вызов каждого из перечислителя должен совпадать с вызовом исходного метода итератора. Поэтому я бы немного изменил оригинальное решение на это:

class MyEnumerator
  include Enumerable

   def initialize(obj, iterator_method)
    @f = Fiber.new do
      @result = obj.send(iterator_method) do |*args|
       Fiber.yield(*args)
      end
      raise StopIteration
    end
   end

   def next(result)
     @f.resume result
   end

   def each
     result = nil
     loop do
      result = yield(self.next(result))
     end
     @result
   end
end

Как отмечалось в других постерах, я считаю, что он создает свое собственное "волокно" [в 1.9]. В 1.8.7 (или 1.8.6, если вы используете гем backports), так или иначе, он делает то же самое (возможно, потому что все потоки в 1.8 эквивалентны волокнам, он просто использует их?)

Таким образом, в 1.9 и 1.8.x, если вы соедините несколько из них вместе a.each_line.map.each_with_index { }

На самом деле он проходит через всю эту цепочку с каждой строкой, как канал в командной строке

http://pragdave.blogs.pragprog.com/pragdave/2007/12/pipelines-using.html

НТН.

Другие вопросы по тегам