Вместо того, чтобы читать рассказ о файберах ниже, вы можете посмотреть скринкаст, в нём затронуты практически те же особенности файберов.
require 'fiber'
fib = Fiber.new do |f1,f2|
f1 ||= 1; f2 ||= 1
loop do
Fiber.yield f1
f1, f2 = f2, f1 + f2
end
end
puts "Первые 10 чисел фибоначчи:"
10.times { puts fib.resume(1,1) }
puts "И еще 5 следующих:"
5.times { print fib.resume(1,1).to_s + ' ' }
Fiber это блок, который может вернуть несколько значений. Вычислив новое значение, Fiber останавливается. Продолжить его работу можно с помощью метода resume.
Чтобы вернуть вычисленное значение из fiber'а, нужно вызвать метод
Fiber.yield с аргументом, равным нужному значению.
Чтобы после создания fiber'а "fib" запустить его, нужно вызвать fib.resume.
При этом аргументы метода resume становятся аргументами блока,
с которым создан fiber (в данном примере это f1 и f2).
Теперь метод resume не завершится до того, пока fiber не вызовет Fiber.yield
(fiber'ы это не thread'ы, они запускаются не параллельно).
После первого вызова resume:
при вызове Fiber.yield его аргументы становятся результатом последнего вызова fib.resume, а сам Fiber.yield внутри кода Filer'а возвращает аргументы следующего вызова fib.resume
при вызове fib.resume его аргументы становятся результатом последнего вызова Fiber.yield, а сам fib.resume возвращает аргументы следующего вызова Fiber.yield
Fiber.yield нужно всегда вызывать изнутри fiber'а, а fiber.resume снаружи.
Ещё один показательный пример:
f = Fiber.new do |x,y|
p "first: " + [x,y].inspect
loop do
p "inside: " + Fiber.yield(3).inspect
end
end
puts "outside: " + f.resume(1, 2).inspect
puts "outside: " + f.resume(1, 2).inspect
puts "outside: " + f.resume(1).inspect
puts "outside: " + f.resume.inspect
Результатом выполнения этой программы будет следующее:
Можно заметить, что когда в метод resume передается больше одного аргумента,
они "заворачиваются" в массив, иначе их нельзя было бы вернуть через возвращаемое
значение Fiber.yield.
Как два однопроходных алгоритма превратить в один однопроходный?
Конечно, сделать это нужно не с помощью copy&paste, а с помощью
метапрограммирования, а именно патчей методов.
def each_number
puts "each number before"
(1..5).each do |n|
yield(n)
end
puts "each number after"
end
def squares
puts "squares before"
each_number do |n|
puts "s(#{n}) = #{n**2}"
end
puts "squares after"
end
def powers
puts "powers before"
each_number do |n|
puts "p(#{n}) = #{2**n}"
end
puts "powers after"
end
# Two-pass algorithm (двупроходный алгоритм)
def do_it
powers
squares
end
############################
# сюда вставьте свой код
#
#########################
do_it
Общая схема такая:
module A
def each_object
# before each_object
loop do
# before iteration i
yield n
end
# after each_object
end
def alg1
# before alg1
each_object do
# alg1 iteration
end
# after alg 1
end
def alg2
# before alg2
each_object do
# alg2 iteration
end
# after alg 2
end
#########################
make_shared_iterator :alg12,
:methods => [:alg1, :alg2],
:for => :each_object
end
include A
alg12
Последовательность выполнения кусков кода должна быть такой:
# before alg1
# before alg2
# before each_object
# before iteration 1
# alg1 iteration
# alg2 iteration
# before iteration 2
# alg1 iteration
# alg2 iteration
# before iteration 3
# alg1 iteration
# alg2_iteration
# after each_object
# after alg 2
# after alg 1
Идея решения на основе Fiber
require 'fiber'
def each_number
puts "each number orig before"
(1..5).each do |n|
yield(n)
end
puts "each number orig after"
end
def squares
puts "squares before"
each_number do |n|
puts "s(#{n}) = #{n**2}"
end
puts "squares after"
end
def powers
puts "powers before"
each_number do |n|
puts "p(#{n}) = #{2**n}"
end
puts "powers after"
end
#################################
$sf = Fiber.new { squares }
$pf = Fiber.new { powers }
alias each_number_orig each_number
$generator = Fiber.new do
puts "generator before"
each_number_orig do |n|
2.times do
Fiber.yield(n)
end
end
puts "generator after"
end
def each_number
puts "each number new before"
loop do
n = $generator.resume()
yield n
Fiber.yield
end
puts "each number new after"
end
# One-pass algorithm (однопроходный алгоритм, включающий в себя два алгоритма)
def do_it
5.times do
$sf.resume
$pf.resume
end
end
do_it
Задачи
Попробуйте не использовать глобальных переменных.
Сделайте так, чтобы можно было вызывать make_shared_iterator с одной и той же опцией :for в одном и том же модуле несколько раз.
Можно ли сделать так, чтобы исходный метод each_number
вернулся в исходное состояние после выполнения do_it?
работал для других потоков (threads) без изменений в любой момент времени?
Как эта задача решалась бы на С++ с помощью шаблона Visitor? Напишите оператор + для класса Visitor.
Напишите для класса Fiber методы each, map, select, ... Важно, чтобы методы map и select были "ленивыми", то есть не выполняли преобразование/фильтрацию пока не будет вызыван метод each.