2009-09-16 19 views
7

Esta pregunta no trata acerca de cómo usar los Enumeradores en Ruby 1.9.1, sino que siento curiosidad sobre cómo funcionan. Aquí hay un código:¿Cómo funcionan los enumeradores en 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 

En el código anterior que puedo utilizar e = Bunk.new.each, y luego e.next, e.next para obtener cada elemento sucesivo, pero ¿qué es exactamente lo suspendía la ejecución y luego reanudar en el lugar correcto?

Soy consciente de que si el rendimiento en 0.upto se reemplaza por Fiber.yield, entonces es fácil de entender, pero ese no es el caso aquí. Es un simple viejo yield, entonces, ¿cómo funciona?

Miré enumerator.c pero es casi incomprensible para mí. Quizás alguien podría proporcionar una implementación en Ruby, utilizando fibras, no 1.8.6 encuestadores basados ​​en la continuación del estilo, ¿eso lo deja todo claro?

Respuesta

12

Aquí está un empadronador rubí claro que utiliza fibras y debe más o menos se comportan igual que el original:

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 

Y antes de que alguien se queja de excepciones como el control de flujo: el empadronador verdadera plantea StopIteration al final, también, por lo Acabo de emular el comportamiento original.

Uso:

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

Actualmente en su e = Bunk.new.each la cláusula else no se ejecuta inicialmente. En cambio, la cláusula 'if! Block_given' ejecuta y devuelve un objeto enumerador. El objeto enumerador mantiene un objeto de fibra internamente. (Al menos eso es lo que parece en el enumerador.c)

Cuando llama a e.each está llamando a un método en un enumerador que utiliza una fibra internamente para realizar un seguimiento de su contexto de ejecución. Este método llama al método Bunk.each utilizando el contexto de ejecución de las fibras. La llamada a Bunk.each aquí ejecuta la cláusula else y cede el valor.

No sé cómo se implementa el rendimiento o cómo una fibra rastrea el contexto de ejecución. No he visto ese código. Casi todo el enumerador y la magia de la fibra se implementa en C.

¿Realmente está preguntando cómo se implementan las fibras y el rendimiento? ¿Qué nivel de detalle estás buscando?

Si estoy fuera de la base, corrígeme.

+0

gracias por su respuesta. sí, estoy pidiendo muchos detalles sobre esto. específicamente me gustaría saber si es posible implementarlo todo en Ruby o si hay algo furtivo en C que no es posible en Ruby. Si es posible implementarlo puramente en Ruby, ¡me encantaría ver el código! :) – horseyguy

1

Como los otros críticos señaló, creo que crea su propio "fibra" privado [en 1,9]. En 1.8.7 (o 1.8.6 si usa la gema backports) de alguna manera u otra hace lo mismo (tal vez porque todos los hilos en 1.8 son el equivalente de fibras, simplemente los usa?)

Así en 1.9 y 1.8.x, si encadena varios de ellos juntos a.each_line.map.each_with_index {}

en realidad fluye a través de toda esa cadena con cada línea, como una especie de tubo en la línea de comandos

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

HTH.

+1

aquí hay una gran descripción detallada http://wiki.github.com/rdp/ruby_tutorials_core/enumerator – rogerdpack

1

Creo que esto sería más preciso.Llamar a cada uno en el enumerador debería ser lo mismo que llamar al método iterador original. Así que cambiaría ligeramente la solución original a esto:

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 
Cuestiones relacionadas