Инструменты метапрограммирования в Ruby

Corban Brook, “Ruby's Metaprogramming Toolbox”, public translation into Russian from English More about this translation.

Translate into another language.

Что такое "метапрограммирование"?

Метапрограммирование — это вид программирования, связанный с созданием программ, которые порождают другие программы как результат своей работы (в частности, на стадии компиляции их исходного кода), либо программ, которые меняют себя во время выполнения (самомодифицирующийся код). Первое позволяет получать программы при меньших затратах времени и усилий на кодирование, чем если бы программист писал их вручную целиком, второе позволяет улучшить свойства кода (размер и быстродействие) (из 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.

Pages: ← previous Ctrl next
1 2 3

Original (English): Ruby's Metaprogramming Toolbox

Translation: © and_rew, George .

translated.by crowd

Like this translation? Share it or bookmark!