Метапрограмування з Ruby Eval: Посібник (Частина 1)

pic

Під час однієї з навчальних сесій на нашому буткемпі ми досліджували eval в Ruby і дізналися, наскільки потужним є мета-програмування в Ruby. Воно дозволяє робити майже все під час виконання програми, від оцінки складних виразів до динамічного створення класів, модулів, методів, змінних та констант.

Для початку, що таке мета-програмування?
Простими словами, мета-програмування — це написання коду, який генерує інший код.

Дозвольте уточнити, ви пишете код, який під час виконання генерує інший код, що потім може бути виконаний динамічно.

Це не просто виконання логіки на кшталт 2+2 або (a + b)²
Або виклик методу динамічно, як ось
send(:my_method)

У Ruby ви можете писати код, який створює класи, модулі та методи, змінні динамічно. Він навіть може розширювати існуючі класи/модулі та робити ще більше.

Базовий Eval

Коли я тільки починав працювати з Ruby, єдине, що я знав про eval, це використання його для оцінки простих виразів. Я думав, що це все; не знав, що він може робити більше.

Ви можете використовувати eval для виконання довільного Ruby-коду, який міститься в рядку, як показано нижче:

eval(“2+ 3+5”)

Хоча eval є потужним, його слід використовувати обережно, оскільки він виконує код в поточному контексті (binding) і може становити загрозу безпеці, якщо його використовувати з ненадійними даними.

Це було дуже просто, але ви можете зробити набагато більше. Давайте розглянемо детальніше.

Оцінка в контексті екземпляра

instance_eval дозволяє оцінювати код в контексті екземпляра об'єкта.
Цей метод особливо корисний, коли вам потрібно:

  • Доступ до змінних екземплярів безпосередньо
  • Визначення методів для конкретних екземплярів
  • Модифікація поведінки окремого об'єкта

Динамічне встановлення або отримання змінних екземплярів

Створімо клас зі змінною екземпляра.

class Person  
 def initialize(name)  
 @name = name  
 end  
end
person = Person.new("Magesh")

Використовуємо eval, щоб динамічно встановити змінну екземпляра таким чином:

eval “person.instance_variable_set(:@name, ‘Dinesh’)”  
puts person.instance_variable_get(:@name) # Output: Dinesh

Використовуємо eval, щоб динамічно отримати змінну екземпляра таким чином:

eval_name = eval “person.instance_variable_get(:@name)”  
puts eval_name # Output: Dinesh

Використання class_eval для створення класу

Створімо метод, який може створювати класи, приймаючи ім'я як аргумент.

def create_dynamic_class(name, methods_to_add)  
 Object.class_eval <<-RUBY  
 class #{name}  
 attr_reader :created_at  

 def initialize(name)  
 @name = name  
 @created_at = Time.now  
 end  

 def greet  
 "Hello! Welcome"  
 end  

 #додавання класового методу  
 def self.description  
 "I am a dynamically created class named #{name}"  
 end  
 end  
 RUBY  
end

Тепер щоразу, коли я хочу створити клас, я можу просто викликати метод ось так:

create_dynamic_class(‘Dog’, {})  

# Виконуємо метод і перевіряємо  
milo = Dog.new(“Milo”)  
Dog.description # => I am a dynamically created class named Dog

Це було просто, правда? Тепер, як додати миттєві методи до цього класу?
Потрібно трохи змінити код, додавши кілька рядків до методу create_dynamic_class.

# Додавання методів екземплярів за допомогою class_eval з блоком  
 klass = Object.const_get(name)  

 # Додавання кожного методу до класу  

 methods_to_add.each do |method_name, method_body|  
 klass.class_eval do  
 define_method(method_name, method_body)  
 end  
 end

Вищезгаданий код візьме хеш, де ключами будуть імена методів, а значеннями — процеси або блоки з реалізацією.

Тепер ми створимо хеш для додавання методів, ось як це робиться.

Це хеш:

my_methods = {  
 greet: -> { “Ruf! Ruf!” },  
 say_bye: -> { “Woof! Woof!” },  
 birth_time: -> { “Born at: #{@created_at}” }  
}

Тепер я маю викликати

create_dynamic_class(“Dog”, my_methods)

Ми можемо протестувати це, щоб перевірити, чи працюють всі методи:

milo = Dog.new(“Milo”)  
milo.greet  
milo.say_bye  
milo.birth_time

Це має дати такий вивід:

#  
“Ruf! Ruf!”  
“Woof! Woof!”  
“Born at: 2024–12–30 14:22:43 -0500”

Бачите, що ми зробили? Ми створили клас динамічно та додали кілька методів екземплярів і класових методів до нього. Все це під час виконання програми. Це було круто, правда? Давайте спробуємо ще одну річ.

Визначення методів для конкретних екземплярів

А що, якщо ми хочемо додавати методи тільки до конкретного екземпляра класу, а не до всіх екземплярів (об'єктів)? У нашому коді вище ми створили собаку на ім'я Milo, але що, якщо є інша собака, і вона може робити те, чого не може Milo?

simba = Dog.new(“Simba”)

А що, якщо Simba може котитися, а Milo не може?

simba.instance_eval do  
 def roll  
 “#{@name} is rolling over!”  
 end  
end

Тепер вище визначений метод roll буде доступний тільки в екземплярі simba.

simba.roll #=> “Simba is rolling over!”  
milo.roll #=> undefined method `roll’ for an instance of Dog (NoMethodError)

Бачите, якщо я викличу метод "roll" на екземплярі "milo", я отримаю помилку "undefined method", оскільки ми додали метод roll тільки на екземпляр "simba". Ось так створюються сінглтон-методи для екземпляра.

Чудово. Це достатньо на зараз. Я покажу більше в Частині 2.

Перейти до Частини 2, щоб дізнатися більше

Перекладено з: Meta programming with Ruby Eval: A guide (Part 1)

Leave a Reply

Your email address will not be published. Required fields are marked *