Код, который пишет код

03 августа 2023
Код, который пишет код

Время летит незаметно и пришла пора выпустить наш августовский Ruby-дайджест. В нём поговорим про метапрограммирование и устроим небольшую Q&A-сессию на тему сборки мусора. Бонусом расскажем, как использовать платформу Jupiter notebook не с Python, а с Ruby.

Метапрограммирование в Ruby

Многие разработчики мечтают освоить метапрограммирование, но придерживаются принципа из фильма «О чём говорят мужчины»: 

«— А так, а так она мечта, её не нужно трогать руками. 

— Слав, не трогай руками!»

Перед тем, как тратить своё драгоценное время, стоит задуматься: «А что даст это знание?». Если кратко, то метапрограммирование поможет:

  • cоблюдать принцип DRY (Don't repeat yourself);
  • эффективно переиспользовать собственный код;
  • легче решать проблемы со сторонними зависимостями, в которых используется метапрограммирование.

Освоение метапрограммирования нельзя назвать простым, это скорее «задача со звёздочкой». Здесь редко встречаются инструменты, позволяющие облегчить жизнь программиста. Поначалу будет много непонятных штук, странного синтаксиса и причудливых блоков кода. Но это всё пройдёт.

В начале даже нет нужды сразу погружаться во все тонкости. Хорошей идеей будет освоить хотя бы три простых метода: send, define_method и method_missing:

Send обеспечивает динамическую передачу методов объектам. При этом необязательно знать, каким будет объект или метод во время написания кода. Вызов делается следующим образом: my_object.send(:method_name). А для передачи дополнительных аргументов используется: my_object.send(:method_name, argument1, argument2...). Send может принимать именованные аргументы или методы. Это даёт возможность сильно упростить код. Например:

def sell_or_refund_or_return_item(item, action)
  if action == "sell"
    item.sell
  elsif action == "refund"
    item.refund
  elsif action == "void"
    item.void
  elsif action == "return"
    item.return
  end
end

Его можно легко переписать, передав метод в качестве аргумента. Вызываем его через send. Profit:

def process_item(item, action)
  return unless item.respond_to?(action)

  item.send(action)
end

Будьте осторожны, поскольку send может вызывать как общедоступные, так и приватные методы. Если надо убедиться, что вызван именно общедоступный метод, лучше воспользоваться public_send.

Если не испугались этого примера и хотите узнать больше об остальных методах, то рекомендуем почитать профильную статью в блоге AppSignal.

Q&A по GC

В одном из наших прошлых дайджестов мы рассказывали, как уберечь приложение от рук OOM-киллера и предотвратить утечки памяти. Сегодня попробуем взглянуть на этот механизм чуть пристальнее в формате Q&A.

Где в Ruby живёт сборщик мусора? В файле gc.c. Помимо сборки мусора, код из этого файла отвечает за выделение и управление памятью. Сборщик мусора полностью контролирует весь жизненный цикл созданных объектов, за редким исключением.

Все ли типы объектов работают одинаково? Формально можно разделить все типы на 2 группы. Первая — immediates. Объекты этой группы типов ведут себя как остальные, но при этом не подчиняются сборщику мусора. Например, nil, true/false, Fixnum (в диапазоне между -(2**62) и 2**62 - 1 на 64-битных системах), числа с плавающей точкой (за исключением некоторых) и статические символы. Вторая группа объектов, heap allocated, целиком полагается на управление сборщиком мусора.

Сколько бывает типов объектов в куче? В общей сложности существует 14 типов объектов.

Какой тип у указателей на объекты Ruby? Тип VALUE.

Из чего состоит куча? Куча состоит из страниц, каждая из которых имеет размер 64 КБ. Далее страницы делятся на слоты фиксированного размера. У каждой страницы слоты имеют единый размер в 40 байт. Если несколько объектов будут повторяться на одной странице памяти, то не потребуется не изменять размеры слотов.

Как выделяется память? Сборщик памяти выделяет память постранично, а не отдельными слотами. Это обусловлено соображениями производительности. Выделять память реже, но большими порциями эффективнее, чем часто, но маленькими.

Как работает Variable Width Allocation? Смысл этой функции в предоставлении разработчикам API для выделения объектов динамического размера. Также она добавляет поддержку слотов переменного размера внутри сборщика мусора. Жаль лишь, что функция не поддерживает все типы объектов.

Что такое fast path? Термин fast path применим к распределению памяти для абстракций Ractor. Каждый экземпляр класса Ractor имеет кэш, который заранее содержит список связанных свободных слотов. Забронировав свободные слоты, несколько экземпляров Ractor могут выделять память объектам параллельно, без риска возникновения условий гонки. Это работает, пока свободные слоты в кэше не кончатся. Тогда придётся использовать slow path. 

Что такое slow path? Каждый Ractor поочерёдно блокирует виртуальную машину Ruby и начинает искать связанные списки свободных слотов по страницам кучи. Как только такая страница найдена, список перемещается в кэш Ractor. Если поиск успехом не увенчался, то система проверяет разрешение на выделение новой страницы кучи и если оно есть, то происходит выделение новой страницы. Отсутствие разрешение означает, что запущен цикл сборки мусора и надо подождать его окончания.

Правда ли, что код Ruby перестаёт выполняться, когда выполняется цикл сборки мусора? Так и есть. Как в знаменитой рекламе «…и пусть весь мир подождёт». В больших приложениях это может вызывать длительные паузы. Чтобы избежать этого, в Ruby применяется два решения: инкрементная маркировка объектов и использование сборщика мусора на основе поколений.

Надеемся, что такая маленькая Q&A-сессия освежила ваши знания. Ну а если хотите узнать ещё подробностей про сборку мусора в Ruby, то советуем заглянуть в статью Garbage Collection in Ruby.

Jupiter notebooks для Ruby

Что приходит на ум, когда вы слышите словосочетание «Jupiter notebook»? Правильно — Python. Исторически так сложилось, что чаще всего функциональность платформы Jupiter notebook требуется специалистам по анализу данных. Те, в свою очередь, задействуют обширный инструментарий, вроде NumPy, pandas и Matplotlib. Все они написаны на Python, так что именно с ним чаще всего приходится иметь дело.

На самом деле, Jupiter notebook отлично сочетается с Ruby. Есть крутой проект IRuby, представляющий собой Ruby-ядро для проекта Jupiter. Вначале установим Jupiter с помощью инсталлятора Python:

pip install jupyter

IRuby поставляется в виде обычного gem. Устанавливаем:

gem install iruby

Регистрируем IRuby в качестве ядра для Jupiter:

iruby register --force

Открываем Jupiter Notebook:

jupyter notebook

Далее Kernel > Change kernel. Там будет доступен Ruby. Осталось создать свой первый Ruby notebook и создать Hello World для проверки:

puts "Hello, world!"

Писать всё в одиночку скучно. Предлагаем привлечь к этому делу генеративную нейросеть от OpenAI. Для этого предусмотрен соответствующий gem. Его можно установить отдельно или указать в качестве зависимости. Можно написать прямо в notebook:

require "bundler/inline"

gemfile do
  source "https://rubygems.org"

  gem "dotenv", require: "dotenv/load"
  gem "ruby-openai"
end

Здесь помимо ruby-openai указан dotenv. Это нужно для удобного хранения токена OpenAI в файле с расширением .env. Его можно будет положить прямо в директорию c notebook. Содержимое файла будет выглядеть так:

OPENAI_ACCESS_TOKEN=your_key

Теперь можно инициализировать клиент строкой:

client = OpenAI::Client.new(access_token: ENV["OPENAI_ACCESS_TOKEN"])

Для проверки возьмём пример из README проекта ruby-openai:

response = client.chat(
  parameters: {
    model: "gpt-3.5-turbo",
    messages: [{ role: "user", content: "Hello!"}],
    temperature: 0.7,
  }
)

Если у вас есть действующая подписка и валидный токен, то система в ответ на приветствие выдаст что-то вроде «Hello! How can I assist you today?». Удачных экспериментов!

Митапы

Онлайн

Ruby meetup

22 ноября 2023

Осенью у нас состоится ещё один Ruby Meetup, который пройдёт в формате онлайн. Программа мероприятия формируется и скоро станет доступна на странице мероприятия. Кстати, если вы не успели подать доклад, то можете это сделать прямо в режиме онлайн. Заявки на участие принимаются до 1 ноября.

Теперь следить за митапами Evrone стало удобнее. В Telegram-канале Evrone meetups мы выкладываем анонсы с подробными описаниями докладов, а также студийные записи после мероприятий. А ещё, у нас можно выступить, мы поможем оформить вашу экспертизу в яркое выступление. Подписывайтесь и пишите @andrew_aquariuss, чтобы узнать подробности.

Регистрация

Вакансии

Удаленка / Офис

Evrone 

Мы открыты для новых Ruby-разработчиков. В Evrone можно работать удалённо с первого дня, мы поддерживаем и оплачиваем участие в Open-source проектах и выступления на конференциях, а расти в грейдах можно с помощью честной системы проверки навыков и менторства.

Подробнее

Подписаться
на Digest →
Важные новости и мероприятия без спама
Технологии которыми вы владеете и которые вам интересны
Ваш адрес электронной почты в безопасности - вот наша политика конфиденциальности.