программные элементы во время выполнения намного упрощают решение задач. Утилиту трассировки выполнения, отладчик, профилировщик — все это легко написать на Ruby и для Ruby. Хорошо известные программы irb
и xmp
, используя динамические возможности Ruby, творят это волшебство.
К подобным возможностям нужно привыкнуть, их легко употребить во вред. Все эти идеи появились отнюдь не вчера (они стары по крайней мере так же, как язык LISP) и считаются «проверенными и доказанными» в сообществах пользователей Scheme и Smalltalk. Даже в языке Java, который так многим обязан С и C++, есть некоторые динамические средства, поэтому мы ожидаем, что со временем их популярность будет только расти.
11.3.1. Динамическая интерпретация кода
Глобальная функция eval
компилирует и исполняет строку, содержащую код на Ruby. Это очень мощный (и вместе с тем опасный) механизм, поскольку позволяет строить подлежащий исполнению код во время работы программы. Например, в следующем фрагменте считываются строки вида «имя = выражение», затем каждое выражение вычисляется, а результат сохраняется в хэше, индексированном именем переменной.
parameters = {}
ARGF.each do |line|
name, expr = line.split(/s*=s*/, 2)
parameters[name] = eval expr
end
Пусть на вход подаются следующие строки:
а = 1
b = 2 + 3
с = 'date'
Тогда в результате мы получим такой хэш: {'а'=>1, 'b'=>5,'с'=>'Mon Apr 30 21:17:47 CDT 2001
'}
. На этом примере демонстрируется также опасность вычисления с помощью eval
строк, содержимое которых вы не контролируете; злонамеренный пользователь может подсунуть строку d= 'rm *'
и стереть всю вашу дневную работу.
В Ruby есть еще три метода, которые интерпретируют код «на лету»: class_eval
, module_eval
и instance_eval
. Первые два — синонимы, и все они выполняют одно и то же: интерпретируют строку или блок, но при этом изменяют значение псевдопеременной self
так, что она указывает на объект, от имени которого эти методы вызваны. Наверное, чаще всего метод class_eval
применяется для добавления методов в класс, на который у вас имеется только ссылка. Мы продемонстрируем это в коде метода hook_method
в примере утилиты Trace
в разделе 11.3.13. Другие примеры вы найдете в динамических библиотечных модулях, например delegate.rb
.
Метод eval
позволяет также вычислять локальные переменные в контексте, не принадлежащем их области видимости. Мы не рекомендуем легкомысленно относиться к этой возможности, но знать, что она существует, полезно.
Ruby ассоциирует локальные переменные с блоками, с определениями высокоуровневых конструкций (класса, модуля и метода) и с верхним уровнем программы (кодом, расположенным вне любых определений). С каждой из этих областей видимости ассоциируются привязки переменных и другие внутренние детали. Наверное, самым главным потребителем информации о привязках является программа irb
— интерактивная оболочка для Ruby, которая пользуется привязками, чтобы отделить собственные переменные от тех, которые принадлежат вводимой программе.
Можно инкапсулировать текущую привязку в объект с помощью метода Kernel#binding
. Тогда вы сможете передать привязку в виде второго параметра методу eval
, установив контекст исполнения для интерпретируемого кода.
def some_method
а = 'local variable'
return binding
end
the_binding = some_method
eval 'a', the_binding # 'local variable'
Интересно, что информация о наличии блока, ассоциированного с методом, сохраняется как часть привязки, поэтому возможны такие трюки:
def some_method
return binding
end
the_binding = some_method { puts 'hello' }
eval 'yield', the_binding # hello
11.3.2. Метод const_get
Метод const_get
получает значение константы с заданным именем из модуля или класса, которому она принадлежит.
str = 'PI'
Math.const_get(str) # Значение равно Math::PI.
Это способ избежать обращения к методу eval
, которое иногда считается неэлегантным. Такой подход дешевле с точки зрения потребления ресурсов и безопаснее. Есть и другие аналогичные методы: instance_variable_set
, instance_variable_get
и define_method
.
Метод const_get
действительно работает быстрее, чем eval
. В неформальных тестах — на 350% быстрее, хотя у вас может получиться другой результат. Но так ли это важно? Ведь в тестовой программе на 10 миллионов итераций цикла все равно ушло менее 30 секунд.
Истинная полезность метода const_get
в том, что его проще читать, он более специфичен и лучше самодокументирован. Даже если бы он был всего лишь синонимом eval
, все равно это стало бы большим шагом вперед.
11.3.3. Динамическое создание экземпляра класса, заданного своим именем
Такой вопрос мы видели многократно. Пусть дана строка, содержащая имя класса; как можно создать экземпляр этого класса?
Правильный способ — воспользоваться методом const_get
, который мы только что рассмотрели. Имена всех классов в Ruby — константы в «глобальном» пространстве имен, то есть члены класса Object
.
classname = 'Array'