программные элементы во время выполнения намного упрощают решение задач. Утилиту трассировки выполнения, отладчик, профилировщик — все это легко написать на 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'

Добавить отзыв
ВСЕ ОТЗЫВЫ О КНИГЕ В ИЗБРАННОЕ

0

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

Отметить Добавить цитату