Вопрос

Этот вопрос не о том, как использовать перечислители в 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, используя fibers, а не счетчики на основе продолжения в стиле 1.8.6, чтобы все было понятно?

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

Решение

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

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

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

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

>> 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.

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

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

Как отмечали другие постеры, я считаю, что это создает свое собственное "волокно" [в 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

ХТХ.

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

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
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top