Инструменты метапрограммирования в Ruby |
- Statistics
- Participants
- Translate into Russian
- Translation result
- Translation complete.
Что такое "метапрограммирование"?
Метапрограммирование — это вид программирования, связанный с созданием программ, которые порождают другие программы как результат своей работы (в частности, на стадии компиляции их исходного кода), либо программ, которые меняют себя во время выполнения (самомодифицирующийся код). Первое позволяет получать программы при меньших затратах времени и усилий на кодирование, чем если бы программист писал их вручную целиком, второе позволяет улучшить свойства кода (размер и быстродействие) (из Wikipedia).
В этом учебнике перечисляются все методы ядра Ruby, используемые при метапрограммировании и демонстрирующие обобщенные способы применения, которые будут полезны для вас. В завершение, представлен пример разработки динамического класса, работающего с базой данных, наподобие ActiveRecord, который автоматически генерирует классы для таблиц базы данных и заполняет каждый класс модели get/set-методами для ее полей.
Инструменты метапрограммирования
Язык Ruby предоставляет множество методов, помогающих при динамической генерации кода. Важно хорошо знать их.
Получение, установка значений и уничтожение переменных
Object#instance_variable_get
Object#instance_variable_set
Object#remove_instance_variable
Module#class_variable_get
Module#class_variable_set
Module#remove_class_variable
Получение, установка значений и уничтожение констант (таких, как классы)
Module#const_get
Module#const_set
Module#remove_const
Добавление и удаление методов
Module#define_method
Module#remove_method
Динамическое выполнение кода
Object#send
Object#instance_eval
Module#module_eval (синоним для Module#class_eval)
Kernel#eval
Kernel#method_missing
Методы для рефлексии
Рефлексия является важной частью метапрограммирования, т.к. позволяет взглянуть на объект и исследовать его содержимое и структуру.
Object#class
Object#instance_variables
Object#methods
Object#private_methods
Object#public_methods
Object#singleton_methods
Module#class_variables
Module#constants
Module#included_modules
Module#instance_methods
Module#name
Module#private_instance_methods
Module#protected_instance_methods
Module#public_instance_methods
Вычисление строк и блоков
Нельзя обойти вниманием eval-методы, которые позволяют вычислять строку или блок как код ruby. Когда нужно вызвать eval в области видимости обычного объекта, можно использовать методы instance_eval и module_eval (синоним для class_eval).
Метод instance_eval работает в области видимости созданного экземпляра объекта.
[1,2,3,4].instance_eval('size') # вернет 4
В приведенном примере методу instance_eval передается строка 'size', которая интерпретируется как метод получаемого объекта. Эта запись эквивалентна следующей:
[1,2,3,4].size
Можно также передать блок методу instance_eval.
# вычислим среднее значение массива целых
[1,2,3,4].instance_eval { inject(:+) / size.to_f } # вернет 2.5
прим.перев.: на ruby 1.8.6 код выдает ошибку LocalJumpError: no block given. Работает такой вариант: [1,2,3,4].instance_eval { inject { |s,e| s + e} / size.to_f }
Обратите внимание, как методы inject(:+) и size.to_f "подвешены в воздухе" без объекта. Это потому, что они вызываются в контексте экземпляра и вычисляются как self.inject(:+) / self.size.to_f, где self — получаемый массив объектов.
Если instance_eval вычисляется для созданного экземпляра объекта, то module_eval вычисляется для модуля или класса.
Fixnum.module_eval do
def to_word
if (0..3).include? self
['none', 'one', 'a couple', 'a few'][self]
elsif self > 3
'many'
elsif self < 0
'negative'
end
end
end
1.to_word # вернет 'one'
2.to_word # вернет 'a couple'
На этом примере видно как module_eval переоткрывает существующий класс Fixnum и добавляет новый метод. В этом нет ничего особенного и аналогичного результата можно добиться другим путем для экземпляра (класса - перев.):
class Fixnum
def to_word
...
end
end
Но по настоящему выгода проявляется при динамической генерации кода. Мы создадим метод класса create_multiplier для динамической генерации метода с выбранным нами именем.
class Fixnum
def self.create_multiplier(name, num)
module_eval "def #{name}; self * #{num}; end"
end
end
Fixnum.create_multiplier('multiply_by_pi', Math::PI)
4.multiply_by_pi # вернет 12.5663706143592
Приведенный пример создает метод класса (синглтон-метод), который при вызове генерирует методы экземпляра, доступные для использования в любом объекте класса Fixnum.
Использование send
Использование send похоже на instance_eval тем, что передается имя метода объекту-получателю. Это полезно, когда нужно динамически получить имя метода из строки или символа
method_name = 'size'
[1,2,3,4].send(method_name) # вернет 4
Можно указать имя метода как строку или символ: 'size' или :size
Потенциальный бонус от использования send — это игнорирование уровня доступа к методу, что позволяет вызывать закрытые методы, такие как Module#define_method.
Array.define_method(:ducky) { puts 'ducky' }
# NoMethodError: private method `define_method' called for Array:Class
Использование "хака" с send:
Array.send(:define_method, :ducky) { puts 'ducky' }
Определение методов
Как видно из примера выше, можно использовать define_method для добавления методов к классам.
class Array
define_method(:multiply) do |arg|
collect{|i| i * arg}
end
end
[1,2,3,4].multiply(16) # вернет [16, 32, 48, 64]
method_missing
Будучи включенным в класс, method_missing получает управление, когда для экземпляра класса вызывается несуществующий метод. Это может быть использовано для отлавливания таких несуществующих методов вместо выбрасывания исключения NoMethodError.
