光纤是您可能永远不会直接在应用程序级代码中使用的东西。它们是流控制原语,可用于构建其他抽象,然后将其用于更高级别的代码中。
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
是Enumerable对象,each
如果使用块调用它们,则它们的方法将产生由原始迭代器方法产生的元素。在我刚刚给出的示例中,由返回的Enumerator reverse_each
具有一个each
产生3,2,1 的方法。由返回的Enumerator 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
。每次对next的调用都必须检查该状态,并在返回值之前对其进行更新。使用光纤,我们可以自动将任何内部迭代器转换为外部迭代器。
这与纤维的使用无关,但是让我再提一提您可以使用Enumerators进行的另一件事:它们允许您将高阶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]
调用无块的迭代器将返回一个Enumerator,然后可以在其上调用其他Enumerable方法。
回到光纤,您是否使用了take
Enumerable中的方法?
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
您还可以使用纤维构建通用的协程设备。我从未在任何程序中使用过协程,但这是一个很好的概念。
我希望这会让您对这些可能性有所了解。正如我在一开始所说的那样,纤维是一种低级的流量控制原语。它们使您可以在程序中维护多个控制流“位置”(如书页中的不同“书签”)并根据需要在它们之间进行切换。由于任意代码都可以在光纤中运行,因此您可以调用光纤上的第三方代码,然后“冻结”它,并在其回调您控制的代码时继续执行其他操作。
想象一下这样的事情:您正在编写一个服务于许多客户端的服务器程序。与客户端的完整交互涉及到一系列步骤,但是每个连接都是短暂的,您必须记住连接之间每个客户端的状态。(听起来像Web编程吗?)
您可以为每个客户端维护一个光纤,而不是显式地存储该状态并在每次客户端连接时对其进行检查(以查看他们所要做的下一个“步骤”是什么)。识别客户端后,您将检索其光纤并重新启动它。然后,在每个连接结束时,您将挂起光纤并再次存储。这样,您可以编写直线代码来实现完整交互的所有逻辑,包括所有步骤(就像您的程序在本地运行一样自然)。
我敢肯定有很多原因使这种事情可能不可行(至少目前是这样),但是我只是想向您展示一些可能性。谁知道; 一旦有了概念,您可能会想出一些全新的应用程序,而其他人还没有想到!