2012-06-15 4 views
5

Según la documentación de Ruby, el objeto Enumerator usa el método each (enumerar) si no se proporciona un método de destino para los métodos to_enum o enum_for. Ahora, tomemos el siguiente parche mono y su encuestador, como un ejemplo¿Cómo itera el objeto Ruby's Enumerator externamente sobre un iterador interno?

o = Object.new 
def o.each 
    yield 1 
    yield 2 
    yield 3 
end 
e = o.to_enum 

loop do 
    puts e.next 
end 

Teniendo en cuenta que el objeto enumerador utiliza el método each de responder cuando next se llama, cómo hacer llamadas a la mirada each método similar, cada vez next se llama? ¿La clase Enumeartor carga todos los contenidos de o.each y crea una copia local para la enumeración? ¿O hay algún tipo de magia Ruby que cuelga las operaciones en cada declaración de rendimiento hasta que se llame al next en el enumerador?

Si se realiza una copia interna, ¿es una copia profunda? ¿Qué pasa con los objetos de E/S que podrían usarse para la enumeración externa?

Estoy usando Ruby 1.9.2.

+2

Para que lo sepas, utiliza comillas invertidas (\ ') alrededor del texto que ver código en línea el formato':) ' –

+0

muchas gracias! Lo tendremos en cuenta para la próxima vez. –

Respuesta

8

No es exactamente mágico, pero es hermoso, no obstante. En lugar de hacer una copia de algún tipo, se usa Fiber para ejecutar primero each en el objeto enumerable de destino. Después de recibir el siguiente objeto de each, Fiber produce este objeto y, por lo tanto, devuelve el control a donde se reanudó inicialmente el Fiber.

Es hermoso porque este enfoque no requiere una copia u otra forma de "copia de seguridad" del objeto enumerable, como se podría imaginar obteniendo por ejemplo llamando al #to_a en el enumerable. La programación cooperativa con fibras permite cambiar contextos exactamente cuando es necesario sin la necesidad de mantener alguna forma de anticipación.

Todo sucede en el C code para Enumerator. Una versión de Ruby puro que mostraría más o menos el mismo comportamiento podría tener este aspecto:

class MyEnumerator 
    def initialize(enumerable) 
    @fiber = Fiber.new do 
     enumerable.each { |item| Fiber.yield item } 
    end 
    end 

    def next 
    @fiber.resume || raise(StopIteration.new("iteration reached an end")) 
    end 
end 

class MyEnumerable 
    def each 
    yield 1 
    yield 2 
    yield 3 
    end 
end 

e = MyEnumerator.new(MyEnumerable.new) 
puts e.next # => 1 
puts e.next # => 2 
puts e.next # => 3 
puts e.next # => StopIteration is raised 
+0

¡Agradable! Voy a leer sobre las fibras con más detalle, pero ¿estos hilos verdes creados por el lenguaje para devolver el control a la persona que llama? En otras palabras, ¿cómo se devuelve el control? –

+0

@SalmanParacha [Wikipedia] (http://en.wikipedia.org/wiki/Fiber_ (computer_science)) hace un mejor trabajo al explicar la diferencia que nunca. Si desea los detalles sangrientos, la implementación está en [cont.c] (https://github.com/ruby/ruby/blob/trunk/cont.c). – emboss

+1

@SalmanParacha: Las fibras son el nombre de Ruby para corutinas. Una coroutine es una generalización de una subrutina: una subrutina * siempre * comienza a ejecutarse desde el principio, y * siempre * regresa a la persona que llama. Una corrutina se ejecuta desde el punto donde se detuvo por última vez y puede "devolver" (o más precisamente transferir el control) a * cualquier * otra corutina, no solo de la que vino. –

Cuestiones relacionadas