纤维是您可能永远不会直接在应用程序级代码中使用的东西。它们是一个流控制原语,您可以使用它来构建其他抽象,然后在较高级别的代码中使用这些抽象。
可能是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