纖維是您可能永遠不會直接在應用程序級代碼中使用的東西。它們是一個流控制原語,您可以使用它來構建其他抽象,然後在較高級別的代碼中使用這些抽象。
可能是Ruby中纖維的第一次使用是實現Enumerator
s,這是Ruby 1.9中的一個核心Ruby類。這些都是令人難以置信的有用。
在Ruby 1.9中,如果您在覈心類上調用幾乎任何迭代器方法,則不通過傳遞一個塊,它將返回Enumerator
。
irb(main):001:0> [1,2,3].reverse_each
=> #<Enumerator: [1, 2, 3]:reverse_each>
irb(main):002:0> "abc".chars
=> #<Enumerator: "abc":chars>
irb(main):003:0> 1.upto(10)
=> #<Enumerator: 1:upto(10)>
這些Enumerator
s爲可枚舉的目的,以及它們的each
方法得到這將已經產生由原始迭代方法中的元素,如果它被稱爲一個塊。在我剛剛給出的例子中,由reverse_each
返回的枚舉器具有產生3,2,1的each
方法。枚舉器返回chars
產生「c」,「b」,「a」(依此類推)。但是,與原迭代法,枚舉也可以通過一個返回的元素之一,如果你反覆在調用它next
:
irb(main):001:0> e = "abc".chars
=> #<Enumerator: "abc":chars>
irb(main):002:0> e.next
=> "a"
irb(main):003:0> e.next
=> "b"
irb(main):004:0> e.next
=> "c"
您可能聽說過「內部迭代器」和「外部迭代器」的(良好的兩者的描述在「四人幫」設計模式書中給出)。上面的例子展示了枚舉器可以用來把一個內部迭代器變成一個外部迭代器。
這是爲了使自己的統計員一個辦法:
class SomeClass
def an_iterator
# note the 'return enum_for...' pattern; it's very useful
# enum_for is an Object method
# so even for iterators which don't return an Enumerator when called
# with no block, you can easily get one by calling 'enum_for'
return enum_for(:an_iterator) if not block_given?
yield 1
yield 2
yield 3
end
end
讓我們試一下:
e = SomeClass.new.an_iterator
e.next # => 1
e.next # => 2
e.next # => 3
等一下...沒有任何東西看起來很奇怪嗎?您將yield
語句編寫爲an_iterator
作爲直線代碼,但枚舉器可以一次運行它們一個。在對next
的調用之間,執行an_iterator
被「凍結」。每次您撥打next
時,它會繼續運行至以下yield
聲明,然後再次「凍結」。
你能猜到這是如何實現的嗎?枚舉器在光纖中將呼叫包裝爲an_iterator
,並且傳遞一個塊,其中暫停光纖。因此,每當an_iterator
產生該塊時,它所運行的光纖被暫停,並且主線程上的執行繼續。下一次呼叫next
時,它將控制權交給光纖,該塊返回,並且an_iterator
繼續從光纖停止。
想想沒有光纖的情況下做什麼需要什麼。每個想要提供內部和外部迭代器的類都必須包含顯式代碼,以跟蹤next
調用之間的狀態。接下來的每次調用都必須檢查該狀態,並在返回值之前更新它。使用光纖,我們可以自動將任何內部迭代器轉換爲外部迭代器。
這不是關於纖維的問題,但讓我再提一點你可以用統計員做的事情:他們允許你將更高階的Enumerable方法應用到除each
以外的其他迭代器。想想看:通常所有可枚舉的方法,包括map
,select
,include?
,inject
,等等,由each
產生的元素都工作。但是如果一個對象除了each
之外還有其他迭代器呢?
irb(main):001:0> "Hello".chars.select { |c| c =~ /[A-Z]/ }
=> ["H"]
irb(main):002:0> "Hello".bytes.sort
=> [72, 101, 108, 108, 111]
調用沒有塊的迭代器會返回一個枚舉器,然後您可以調用其他Enumerable方法。
回到纖維,你有沒有使用Enumerable中的take
方法?
class InfiniteSeries
include Enumerable
def each
i = 0
loop { yield(i += 1) }
end
end
如果有任何調用each
方法,它看起來像它應該不會再回來了吧?看看這個:
InfiniteSeries.new.take(10) # => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
我不知道這是否使用纖維罩下,但它可以。纖維可用於實現無限列表和一系列懶惰評估。有關枚舉器定義的一些惰性方法的示例,我在此定義了一些:https://github.com/alexdowad/showcase/blob/master/ruby-core/collections.rb
您也可以使用光纖構建通用協同工具。我從未在我的任何程序中使用過協程,但這是一個很好的概念。
我希望這給你一些可能性的想法。正如我剛開始所說的,光纖是一種低級別的流控制原語。它們可以在程序中保持多個控制流「位置」(如書中不同的「書籤」),並根據需要在它們之間切換。由於任意代碼都可以在光纖中運行,因此您可以在光纖上調用第三方代碼,然後「凍結」該代碼並在回調到您控制的代碼時繼續執行其他操作。
想象一下這樣的事情:您正在編寫一個服務器程序,它將爲許多客戶端提供服務。與客戶端的完整交互涉及經歷一系列步驟,但每個連接都是暫時的,並且您必須記住每個客戶端在連接之間的狀態。 (聽起來像網絡編程?)
而不是顯式存儲該狀態,並檢查每次客戶端連接(看看他們要做的下一個「步驟」),您可以爲每個客戶端維護光纖。識別客戶端後,您將檢索其光纖並重新啓動它。然後在每次連接結束時,您將暫停光纖並再次存儲。這樣,您可以編寫直線代碼來實現完整交互的所有邏輯,包括所有步驟(就像您的程序在本地運行時一樣)。
我確定有很多原因可能導致這樣的事情不可行(至少現在),但我只是想告訴你一些可能性。誰知道;一旦你得到了這個概念,你可能會想出一些全新的應用程序,這是別人沒有想到的!
舊的斐波納契例子只是最差的可能動機;-)甚至有一個公式可以用來計算O(1)中的_any_斐波那契數。 – usr 2012-01-29 12:30:03
問題不在於算法,而在於理解纖維:) – fl00r 2012-01-29 18:20:12