2012-06-15 9 views
5

Zgodnie z dokumentacją Ruby, obiekt Enumerator używa metody each (do wyliczenia), jeśli nie podano metody docelowej dla metod to_enum lub enum_for. Teraz weźmy następującą poprawkę małpa i jego wyliczający, jako przykładW jaki sposób moduł wyliczający Ruby dokonuje iteracji zewnętrznej na wewnętrznym iteratorze?

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

loop do 
    puts e.next 
end 

Biorąc pod uwagę, że obiekt Enumerator wykorzystuje metodę each odpowiedzieć kiedy next nazywa się, w jaki sposób połączenia do wyglądu each metoda jak za każdym razem, next nazywa się? Czy klasa Enumeartor wstępnie ładuje całą zawartość o.each i tworzy lokalną kopię do wyliczenia? Czy istnieje jakaś magia rubinowa, która zawiesza operacje na każdym zestawieniu rachunku zysków, aż do wywołania next w enumeartorze?

Jeśli wykonano kopię wewnętrzną, czy jest to kopia głęboka? A co z obiektami we/wy, które mogą być używane do zewnętrznego wyliczania?

Używam Ruby 1.9.2.

+2

tak wiesz, użyć odwrócone, pojedyncze apostrofy (\ ') wokół tekstu zrobić inline kod formatowania':) ' –

+0

Muchas gracias! Będzie o tym pamiętać następnym razem. –

Odpowiedz

8

To nie jest magia, ale mimo wszystko jest piękna. Zamiast wykonywania jakiejś kopii, Fiber jest używany do pierwszego wykonania each na docelowym obiekcie przeliczalnym. Po otrzymaniu następnego obiektu each, Fiber ustala ten obiekt i w ten sposób przywraca kontrolę z powrotem do miejsca, w którym początkowo wznowiono Fiber.

To piękne, ponieważ takie podejście nie wymaga kopii ani innej formy "kopii zapasowej" przeliczalnego obiektu, jak można sobie wyobrazić, uzyskując na przykład wywołanie #to_a na przeliczalnym. Wspólne planowanie z użyciem włókien pozwala na zmianę kontekstów dokładnie wtedy, gdy jest to potrzebne, bez potrzeby zachowania jakiejś formy wyprzedzenia.

Wszystko dzieje się w C code dla Enumerator. Czysta wersja Ruby, które wykazują mniej więcej takie samo zachowanie może wyglądać następująco:

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

Nice! Przeczytam więcej o włóknach, ale czy te zielone nitki są stworzone przez ten język, aby zwrócić kontrolę dzwoniącemu? Innymi słowy, w jaki sposób zwracana jest kontrola? –

+0

@SalmanParacha [Wikipedia] (http://en.wikipedia.org/wiki/Fiber_ (computer_science)) ma lepszą pracę w wyjaśnianiu różnicy, niż kiedykolwiek mogłem. Jeśli chcesz poznać szczegóły, implementacja jest w [cont.c] (https://github.com/ruby/ruby/blob/trunk/cont.c). – emboss

+1

@SalmanParacha: Fibres są nazwiskiem Ruby dla coroutines. Coroutine to uogólnienie podprogramu: podprogram * zawsze * zaczyna biec od początku, a * zawsze * wraca z powrotem do osoby dzwoniącej. Coroutine biegnie od punktu, w którym został ostatnio zatrzymany i może "powrócić" (lub dokładniej przenieść kontrolę) do * dowolnego * innego coroutine, nie tylko tego, z którego pochodzi. –

Powiązane problemy