Листинг 11.15. Генератор чисел Фибоначчи
class Generator
def initialize
do_generation
end
def next
callcc do |here|
@main_context = here;
@generator_context.call
end
end
private
def do_generation
callcc do |context|
@generator_context = context;
return
end
generating_loop
end
def generate(value)
callcc do |context|
@generator_context = context;
@main_context.call(value)
end
end
end
# Порождаем подкласс и определяем метод generating_loop.
class FibGenerator < Generator
def generating_loop
generate(1)
a, b = 1, 1
loop do
generate(b)
a, b = b, a+b
end
end
end
# Создаем объект этого класса...
fib = FibGenerator.new
puts fib.next # 1
puts fib.next # 1
puts fib.next # 2
puts fib.next # 3
puts fib.next # 5
puts fib.next # 8
puts fib.next # 13
# И так далее...
Есть, конечно, и более практичные применения продолжений. Один из примеров — каркас Borges
для разработки Web-приложений (названный в честь Хорхе Луиса Борхеса), который построен по образу Seaside
. В этой парадигме традиционный поток управления в Web-приложении «вывернут с изнанки на лицо», так что логика представляется «нормальной». Например, вы отображаете страницу, получаете результат из формы, отображаете следующую страницу и так далее, ни в чем не противореча интуитивным ожиданиям.
Проблема в том, что продолжение — «дорогая» операция. Необходимо сохранить состояние и потратить заметное время на переключение контекста. Если производительность для вас критична, прибегайте к продолжениям с осторожностью.
11.2.6. Хранение кода в виде объекта
Неудивительно, что Ruby предлагает несколько вариантов хранения фрагмента кода в виде объекта. В этом разделе мы рассмотрим объекты Proc
, Method
и UnboundMethod
.
Встроенный класс Proc
позволяет обернуть блок в объект. Объекты Proc
, как и блоки, являются замыканиями , то есть запоминают контекст, в котором были определены.
myproc = Proc.new { |a| puts "Параметр равен #{а}" }
myproc.call(99) # Параметр равен 99
Кроме того, Ruby автоматически создает объект Proc, когда метод, последний параметр которого помечен амперсандом, вызывается с блоком в качестве параметра:
def take_block(x, &block)
puts block.class
x.times {|i| block[i, i*i] }
end
take_block(3) { |n,s| puts "#{n} в квадрате равно #{s}" }
В этом примере демонстрируется также применение квадратных скобок как синонима метода call
. Вот что выводится в результате исполнения:
Proc
0 в квадрате 0
1 в квадрате 1
2 в квадрате 4
Объект Proc
можно передавать методу, который ожидает блок, предварив имя знаком &
:
myproc = proc { |n| print n, "... " }
(1..3).each(&myproc) # 1... 2... 3...
Ruby позволяет также превратить метод в объект. Исторически для этого применяется метод Object#method
, который создает объект класса Method
как замыкание в конкретном объекте.
str = "cat"
meth = str.method(:length)
a = meth.call # 3 (длина "cat")
str << "erpillar"
b = meth.call # 11 (длина "caterpillar")
str = "dog"
# Обратите внимание на следующий вызов! Переменная str теперь ссылается
# на новый объект ("dog"), но meth по-прежнему связан со старым объектом.
с = meth.call # 11 (длина "caterpillar")
Начиная с версии Ruby 1.6.2, можно также применять метод Module#instance_method
для создания объектов UnboundMethod
. С их помощью представляется метод, ассоциированный с классом, а не с конкретным объектом. Прежде чем вызывать объект UnboundMethod
, нужно связать его с каким-то объектом. Результатом операции связывания является объект Method
, который можно вызывать как обычно:
umeth = String.instance_method(:length)
m1 = umeth.bind("cat")
m1.call # 3
m2 = umeth.bind("caterpillar")
m2.call # 11
Явное связывание делает объект UnboundMethod
интуитивно более понятным, чем Method
.
11.2.7. Как работает включение модулей?
Когда модуль включается в класс, Ruby на самом деле создает прокси-класс, являющийся непосредственным родителем данного класса. Возможно, вам это покажется интуитивно очевидным, возможно, нет. Все методы включаемого модуля «маскируются» методами, определенными в классе.
module MyMod
def meth
"из модуля"
end
end
class ParentClass
Читать дальше
Конец ознакомительного отрывка
Купить книгу