Как работают счетчики в Ruby 1.9.1?
Этот вопрос не о том, как использовать перечислители в Ruby 1.9.1, а о том, как они работают. Вот некоторый код:
class Bunk
def initialize
@h = [*1..100]
end
def each
if !block_given?
enum_for(:each)
else
0.upto(@h.length) { |i|
yield @h[i]
}
end
end
end
В приведенном выше коде я могу использовать e = Bunk.new.each
, а потом e.next
, e.next
получить каждый последующий элемент, но как именно он приостанавливает выполнение и затем возобновляет работу в нужном месте?
Я знаю, что если доходность в 0.upto
заменяется на Fiber.yield
тогда это легко понять, но здесь дело не в этом. Это старый добрый yield
, Итак, как это работает?
Я посмотрел на enumerator.c, но мне все еще непонятно. Может быть, кто-то мог бы предоставить реализацию в Ruby, используя волокна, а не перечислители на основе продолжения стиля 1.8.6, что бы все прояснило?
4 ответа
Вот простой перечислитель ruby, который использует Fibers и должен вести себя как оригинал:
class MyEnumerator
include Enumerable
def initialize(obj, iterator_method)
@f = Fiber.new do
obj.send(iterator_method) do |*args|
Fiber.yield(*args)
end
raise StopIteration
end
end
def next
@f.resume
end
def each
loop do
yield self.next
end
rescue StopIteration
self
end
end
И прежде, чем кто-то будет жаловаться на исключения в качестве управления потоком: настоящий Enumerator также вызывает StopItered в конце, поэтому я просто эмулировал исходное поведение.
Использование:
>> enum = MyEnumerator.new([1,2,3,4], :each_with_index)
=> #<MyEnumerator:0x9d184f0 @f=#<Fiber:0x9d184dc>
>> enum.next
=> [1, 0]
>> enum.next
=> [2, 1]
>> enum.to_a
=> [[3, 2], [4, 3]]
На самом деле в вашем e = Bunk.new.each предложение else изначально не выполняется. Вместо этого выполняется условие if! Block_given, которое возвращает объект перечислителя. Объект перечислителя действительно сохраняет объект волокна внутри. (По крайней мере, так это выглядит в enumerator.c)
Когда вы вызываете e.each, он вызывает метод для перечислителя, который использует внутреннее волокно для отслеживания его контекста выполнения. Этот метод вызывает метод Bunk.each, используя контекст выполнения волокон. Вызов Bunk.each здесь выполняет условие else и возвращает значение.
Я не знаю, как реализован yield или как волокно отслеживает контекст выполнения. Я не смотрел на этот код. Почти все перечислители и волоконные магики реализованы на C.
Вы действительно спрашиваете, как реализованы волокна и урожайность? Какой уровень детализации вы ищете?
Если я с базы, поправьте меня.
Я думаю, что это будет более точным. Вызов каждого из перечислителя должен совпадать с вызовом исходного метода итератора. Поэтому я бы немного изменил оригинальное решение на это:
class MyEnumerator
include Enumerable
def initialize(obj, iterator_method)
@f = Fiber.new do
@result = obj.send(iterator_method) do |*args|
Fiber.yield(*args)
end
raise StopIteration
end
end
def next(result)
@f.resume result
end
def each
result = nil
loop do
result = yield(self.next(result))
end
@result
end
end
Как отмечалось в других постерах, я считаю, что он создает свое собственное "волокно" [в 1.9]. В 1.8.7 (или 1.8.6, если вы используете гем backports), так или иначе, он делает то же самое (возможно, потому что все потоки в 1.8 эквивалентны волокнам, он просто использует их?)
Таким образом, в 1.9 и 1.8.x, если вы соедините несколько из них вместе a.each_line.map.each_with_index { }
На самом деле он проходит через всю эту цепочку с каждой строкой, как канал в командной строке
http://pragdave.blogs.pragprog.com/pragdave/2007/12/pipelines-using.html
НТН.