2009-09-16 14 views
7

To pytanie nie dotyczy tego, jak używać enumeratorów w Rubim 1.9.1, ale raczej jestem ciekawy jak one działają. Oto kod:Jak działają enumeratory w Rubim 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 

W powyższym kodzie mogę używać e = Bunk.new.each, a następnie e.next, e.next dostać każdego kolejnego elementu, ale jak dokładnie jest to zawieszając wykonanie, a następnie wznowienie na właściwym miejscu?

Jestem świadomy, że jeśli wydajność w 0.upto zostanie zastąpiona wartością Fiber.yield, łatwo jest to zrozumieć, ale tak nie jest w tym przypadku. To jest zwykły stary yield, więc jak to działa?

Spojrzałem na enumerator.c, ale jest to niezrozumiałe dla mnie. Być może ktoś mógłby zapewnić implementację w Ruby, używając światłowodów, a nie opartych na 1.8.6 stylowych modułów wyliczających, które to wszystko wyjaśnia?

Odpowiedz

12

Oto zwykły rubin wyliczający który wykorzystuje włókna i powinno dość dużo zachowują się jak oryginał:

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 

I zanim ktoś narzeka wyjątkami jak kontrola przepływu: Prawdziwy Enumerator podnosi StopIteration na końcu też tak Po prostu naśladowałem oryginalne zachowanie.

Zastosowanie:

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

Właściwie w Twojej e = Bunk.new.each klauzula else nie jest wykonywana początkowo. Zamiast tego klauzula "if! Block_given" wykonuje i zwraca obiekt modułu wyliczającego. Obiekt modułu wyliczającego zachowuje obiekt światłowodowy wewnętrznie. (Przynajmniej tak to wygląda w enumerator.c)

Podczas wywoływania e.each wywołuje metodę w module wyliczającym, który wewnętrznie wykorzystuje światłowód do śledzenia jego kontekstu wykonania. Ta metoda nazywa metodę Bunk.each przy użyciu kontekstu wykonywania włókien. Wywołanie Bunk.each tutaj wykonuje klauzulę else i podnosi wartość.

Nie wiem, w jaki sposób uzyskano wydajność lub jak światłowód śledzi kontekst wykonania. Nie patrzyłem na ten kod. Prawie wszystkie moduły wyliczające i światłowodowe są zaimplementowane w C.

Czy naprawdę pytasz, w jaki sposób wdrażane są włókna i wydajność? Jakiego poziomu szczegółowości szukasz?

Jeśli jestem poza bazą, popraw mnie.

+0

dzięki za odpowiedź. tak, proszę o dość dużo szczegółów na ten temat. konkretnie chciałbym wiedzieć, czy możliwe jest zaimplementowanie tego wszystkiego w Ruby lub czy jest coś podstępnego w C, co nie jest możliwe w Ruby. Jeśli możliwe jest zaimplementowanie go wyłącznie w Ruby, chciałbym zobaczyć kod! :) – horseyguy

1

W innych plakatów zauważyć, uważam, że tworzy własną „błonnik” [w 1,9]. W 1.8.7 (lub 1.8.6 jeśli używasz klejnotów z backportami) w jakiś sposób robi to samo (być może dlatego, że wszystkie wątki w 1.8 są odpowiednikami włókien, po prostu używa ich?)

Tak więc w 1.9 i 1.8.x, jeśli łańcuch kilku z nich razem a.each_line.map.each_with_index {}

To faktycznie przepływa przez ten cały łańcuch z każdej linii, coś w rodzaju rury w linii poleceń

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

HTH.

+1

tutaj jest wielki szczegółowy opis http://wiki.github.com/rdp/ruby_tutorials_core/enumerator – rogerdpack

1

Myślę, że byłoby to bardziej dokładne.Wywołanie każdego z modułów wyliczających powinno być takie samo jak wywołanie oryginalnej metody iteratora. Więc nieznacznie zmienię oryginalne rozwiązanie na:

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