2012-06-15 40 views
5

根據Ruby的文檔,如果沒有爲to_enumenum_for方法提供目標方法,則枚舉器對象使用each方法(枚舉)。現在,讓我們採取以下猴補丁和枚舉,作爲一個例子Ruby的Enumerator對象如何通過內部迭代器在外部迭代?

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

loop do 
    puts e.next 
end 

鑑於枚舉對象使用each方法回答時next叫,怎麼辦的each方法看來電一樣,每次next被稱爲? Enumeartor類是否預加載o.each的所有內容並創建枚舉的本地副本?或者是否有某種Ruby魔法會在每個yield語句中掛起操作,直到在enumeartor上調用next

如果創建了內部副本,是否是深層副本?那麼可以用於外部枚舉的I/O對象呢?

我正在使用Ruby 1.9.2。

+2

只要你知道,你用文本週圍反引號(\')做行內代碼格式化':)' –

+0

是歡迎您!下次要記住這一點。 –

回答

8

這不完全是神奇的,但它是美麗的。使用Fiber來首先在目標可枚舉對象上執行each,而不是製作某種類型的副本。在收到each的下一個對象後,Fiber產生該對象,從而將控制返回到最初恢復的位置。

這很漂亮,因爲這種方法不需要複製或其他形式的可備份對象的「備份」,正如人們可以想象的通過例如在可枚舉上調用#to_a來獲得的。使用光纖進行合作調度可以在需要時切換上下文,而無需保留某種形式的預覽。

這一切都發生在C codeEnumerator。純Ruby的版本,將顯示大致相同的行爲看起來是這樣的:

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

不錯!我會更詳細地閱讀光纖,但是這些綠色線程是由語言創建的將控制權返回給調用者的綠色線程?換句話說,控制是如何返回的? –

+0

@SalmanParacha [Wikipedia](http://en.wikipedia.org/wiki/Fiber_(computer_science))在解釋差異方面比我所能做得更好。如果你想了解更多細節,可以參考[cont.c](https://github.com/ruby/ruby/blob/trunk/cont.c)。 – emboss

+1

@SalmanParacha:纖維是Ruby的協程名稱。協程是子例程的泛化:子例程* always *從頭開始運行,它總是*返回給調用者。協程從它上次停止的時刻開始運行,它可以「返回」(或更精確地說,轉移控制)到任何其他協程,而不僅僅是它來自哪個協程。 –