2009-09-16 14 views
7

Questa domanda non riguarda l'uso degli Enumeratori in Ruby 1.9.1, ma piuttosto sono curioso di sapere come funzionano. Ecco il codice:Come funzionano gli enumeratori in 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 

Nel codice di cui sopra posso usare e = Bunk.new.each, e poi e.next, e.next per ottenere ogni elemento successivo, ma esattamente come è vero sospendendo l'esecuzione e poi riprendere al punto giusto?

Sono consapevole che se il rendimento nel 0.upto viene sostituito con Fiber.yield, allora è facile da capire, ma non è questo il caso. È un semplice vecchio yield, quindi come funziona?

Ho guardato enumerator.c ma è quasi impossibile per me. Forse qualcuno potrebbe fornire un'implementazione in Ruby, usando le fibre, non gli enumeratori basati sulla continuazione dello stile 1.8.6, che rende tutto chiaro?

risposta

12

Ecco un enumeratore rubino chiaro che utilizza fibre e dovrebbe praticamente comportarsi come l'originale:

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 

E prima che qualcuno si lamenta per il controllo del flusso eccezioni: Il vero Enumerator solleva StopIteration alla fine, anche, in modo Ho appena emulato il comportamento originale.

Usage:

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

In realtà nella tua e = Bunk.new.each la clausola else non viene eseguita inizialmente. Invece la clausola 'if! Block_given' esegue e restituisce un oggetto enumeratore. L'oggetto enumeratore mantiene un oggetto fibra internamente. (Almeno questo è quello che appare in enumerator.c)

Quando si chiama e.each si chiama un metodo su un enumeratore che utilizza una fibra internamente per tenere traccia del suo contesto di esecuzione. Questo metodo chiama il metodo Bunk.each usando il contesto di esecuzione delle fibre. La chiamata Bunk.each qui esegue la clausola else e restituisce il valore.

Non so come viene resa la resa o come una fibra tiene traccia del contesto di esecuzione. Non ho guardato quel codice. Quasi tutta la magia di enumerazione e fibra è implementata in C.

Stai davvero chiedendo come vengono implementate le fibre e la resa? Che livello di dettaglio stai cercando?

Se sono fuori base, correggimi.

+0

grazie per la risposta. sì, sto chiedendo un sacco di dettagli su questo. in particolare vorrei sapere se è possibile implementarlo tutto in Ruby o se c'è qualcosa di subdolo in C che non è possibile in Ruby. Se è possibile implementarlo esclusivamente in Ruby, mi piacerebbe vedere il codice! :) – horseyguy

1

Come gli altri manifesti notato, credo che crea la sua propria "fibra" privata [in 1.9]. In 1.8.7 (o 1.8.6 se usi la gemma dei backport) in qualche modo fa la stessa cosa (forse perché tutti i thread in 1.8 sono l'equivalente di fibre, li usa solo?)

Così in 1.9 e 1.8.x, se si concatenano alcuni di loro insieme a.each_line.map.each_with_index {}

Esso scorre effettivamente attraverso tutta quella catena con ciascuna linea, come una sorta di tubo sulla linea di comando

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

HTH.

+1

ecco una descrizione dettagliata grande http://wiki.github.com/rdp/ruby_tutorials_core/enumerator – rogerdpack

1

Penso che questo sarebbe più accurato.Chiamare ciascuno sull'enumeratore dovrebbe essere lo stesso di chiamare il metodo iteratore originale. Quindi cambierei leggermente la soluzione originale a questo:

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