Раздел «Язык Ruby».RubyCourseLecture14:
<<Метапрограммирование на Ruby

Следующая лекция
Предыдущая лекция

Лекция 14: Fibers. Make shared iterator

Fibers

Вместо того, чтобы читать рассказ о файберах ниже, вы можете посмотреть скринкаст, в нём затронуты практически те же особенности файберов.

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 нужно всегда вызывать изнутри 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

Результатом выполнения этой программы будет следующее:

first: [1, 2]
outside: 3
inside: [1, 2]
outside: 3
inside: 1
outside: 3
inside: nil
outside: 3

Можно заметить, что когда в метод 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

Задачи

Следующая лекция
Предыдущая лекция

Attachment sort Action Size Date Who Comment
fiber.rb manage 1.1 K 20 Apr 2010 - 10:00 ArtemVoroztsov