2009-09-16 5 views
7

Этот вопрос не о том, как использовать Enumerators в Ruby 1.9.1, но мне интересно, как они работают. Вот код:Как работают Enumerators в 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, основанные на продолжении перечислителей, что делает все ясно?

ответ

12

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

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 поднимает 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]] 
4

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

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

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

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

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

+0

благодарит за ваш ответ. да, я задаю довольно много подробностей об этом. в частности, я хотел бы знать, можно ли реализовать все это в Ruby или есть что-то скрытое в C, что невозможно в Ruby. Если это возможно реализовать в Ruby, я бы хотел увидеть код! :) – horseyguy

1

Как и другие плакаты отметил, я считаю, что это создает свой собственный "волокно" [в 1.9]. В 1.8.7 (или 1.8.6, если вы используете драгоценный камень заднего плана) так или иначе он делает то же самое (возможно, потому, что все нити в 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

HTH.

+1

вот отличное подробное описание http://wiki.github.com/rdp/ruby_tutorials_core/enumerator – rogerdpack

1

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

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