2009-09-16 4 views
7

Cette question n'est pas sur la façon d'utiliser les énumérateurs dans Ruby 1.9.1 mais plutôt je suis curieux de savoir comment ils fonctionnent. Voici un code:Comment les énumérateurs fonctionnent-ils dans 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 

Dans le code ci-dessus je peux utiliser e = Bunk.new.each, puis e.next, e.next pour obtenir chaque élément successif, mais comment exactement il suspendait l'exécution, puis reprendre au bon endroit?

Je suis conscient que si le rendement dans le 0.upto est remplacé par Fiber.yield alors il est facile à comprendre, mais ce n'est pas le cas ici. C'est un vieux yield, alors comment ça marche?

J'ai regardé enumerator.c mais il est hype incompréhensible pour moi. Peut-être que quelqu'un pourrait fournir une implémentation dans Ruby, en utilisant des fibres, et non pas des énumérateurs basés sur la continuité de style 1.8.6, cela rend tout cela clair?

Répondre

12

Voici un recenseur rubis clair qui utilise des fibres et devrait à peu près se comporter comme l'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 

Et avant que quelqu'un se plaint d'exceptions que le contrôle de flux: Le vrai recenseur soulève StopIteration à la fin, aussi, donc J'ai juste émulé le comportement original.

Utilisation:

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

En fait, dans votre e = Bunk.new.each, la clause else n'est pas exécutée initialement. A la place, la clause 'if! Block_given' exécute et retourne un objet énumérateur. L'objet énumérateur conserve un objet fibre en interne. Lorsque vous appelez e.each, vous appelez une méthode sur un énumérateur qui utilise une fibre en interne pour garder une trace de son contexte d'exécution. Cette méthode appelle la méthode Bunk.each en utilisant le contexte d'exécution des fibres. L'appel Bunk.each exécute ici la clause else et renvoie la valeur.

Je ne sais pas comment le rendement est implémenté ou comment une fibre suit le contexte d'exécution. Je n'ai pas regardé ce code. Presque tout l'énumérateur et la magie des fibres sont implémentés dans C.

Demandez-vous vraiment comment les fibres et le rendement sont implémentés? Quel niveau de détail recherchez-vous?

Si je suis hors base s'il vous plaît corrigez-moi.

+0

merci pour votre réponse. oui je demande beaucoup de détails à ce sujet. Plus précisément, j'aimerais savoir s'il est possible de tout mettre en œuvre dans Ruby ou s'il y a quelque chose de sournois dans C qui n'est pas possible dans Ruby. S'il est possible de l'implémenter purement en Ruby, j'aimerais voir le code! :) – horseyguy

1

Comme les autres affiches noté, je crois qu'il crée sa propre privée "fibre" [1.9]. Dans 1.8.7 (ou 1.8.6 si vous utilisez la gemme backports) d'une manière ou d'une autre, il fait la même chose (peut-être parce que tous les threads dans 1.8 sont l'équivalent de fibres, il les utilise simplement?)

et 1.8.x, si vous enchaînez plusieurs d'entre eux ensemble a.each_line.map.each_with_index {}

Il coule en fait à travers cette chaîne entière avec chaque ligne, un peu comme un tuyau sur la ligne de commande

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

HTH.

+1

voici une grande description détaillée http://wiki.github.com/rdp/ruby_tutorials_core/enumerator – rogerdpack

1

Je pense que ce serait plus précis.Appeler chacun sur l'énumérateur devrait être le même que d'appeler la méthode d'itérateur d'origine. Donc, je voudrais changer légèrement la solution originale à ceci:

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