D:\sideБлогRuby — написать новый язык?

⚠️ Обращайте внимание на даты.
Этот блог больше не ведётся с 17 января 2023, и на тот момент с написания этой страницы (31.03.2014) прошло 8 лет.

Для начала расшифрую заголовок, хотя если предыдущий пост вы читали, вы уже знаете. Речь пойдёт о языке программирования Ruby. Как я уже писал, гибкость синтаксиса Ruby располагает к сооружению на его основе “новых языков” (кавычки не просто так). И именно Ruby хорош для этих целей тем, что у нового языка получается синтаксис, от которого не скручиваются мозги.

Один из показателей того, чем он лучше - “читабельность” языка, он выглядит весьма похоже на английский текст, разве что слегка сдобренный значками и скобочками (и без скобочек можно обойтись). Один из примеров я приводил в прошлом посте: аналогичный приведу и здесь, чтобы лишний раз не напрягать переходами по ссылкам. Тем не менее, прошлый пост к прочтению перед этим крайне желателен.

every 1.days do
  runner "Item.cleanup"
end

Это расписание. Оно взято из реальной софтины, что я пишу сейчас. И само по себе оно ничего не делает. Но стоит проехаться по проекту с расписанием с помощью whenever -w (whenever с флагом -w, write, записать), и понятное на вид расписание будет преобразовано в куда менее понятный (лично мне и многим другим) синтаксис cron (занимается запуском всякой ерунды в Linux по расписанию) и записано в соответствующее место системы. Гораздо проще работать с уже известным синтаксисом, этим и обусловлена популярность этой библиотеки (Whenever Ruby gem).

И в отличие от многих слоёв поверх других языков — мы имеем дело не с вложенным интерпретатором, а транслятором. То есть, он не будет каждый раз разбирать разметку расписания при проверке “не пора ли чего сделать”, а один раз преобразует её в разметку, понятную другой программе.

Из этого, вероятно, не очень понятно, что здесь имеется в виду под DSL. Ничего общего с модемами, нет. Речь об “узкоспециализированных языках”, которые выполняют одну узкую задачу, для которых они предназначены. В случае с Whenever это добавление задач в расписание системы, но всяческих областей же много. Некоторые даже пишут саркастические посты о том, “как сделать успешный gem для Ruby”, раскладывая разработку на последовательность вроде этой:

  1. Выбрать область для языка (решаемую им проблему).
  2. Придумать крутой синтаксис для языка.
  3. Придумать для итогового gem’а короткое и цепляющее название.
  4. Написать код.
  5. ???
  6. PROFIT

Из этого вытекает одна из самых больших проблем Ruby. И всяческих фреймворков на его основе. Проблема в том, что даже если вы знаете Ruby, пользоваться сходу произвольной библиотекой, которая вам понадобилась, вы не сможете, нужно знать ключевые слова из синтаксиса самой библиотеки. И так получилось, что языки появляются быстрее, чем их успевают документировать. Практикующих программистов больше, чем документирующих. Поэтому при работе с Ruby самая страшная проблема — отсутствие документации. Нужно быть морально готовым лезть в исходный код вашей библиотеки и читать, что она там делает, какие параметры для неё имеют смысл. Часто “документированность” является решающим фактором в выборе библиотек.

А в сущности, Ruby для удобства написания языков содержит не так много.

Возьмём, например, функции, которые принимают код. О, придумал. Давайте напишем GM-овский repeat:

def repeat(n, &block)
  1.upto(n) do
    yield # неочевидная строчка, синоним block.call
  end
end

repeat(5) {
  puts "Hi!"
}

Не отличить, да? Но это только если использовать С-подобный GML, на котором пишу я. Есть ещё Pascal-подобный синтаксис с begin и end. Мне в голову не приходит способов их сделать, но в Ruby есть блоки do-end. Они на вид не хуже, но их дольше писать, и я ими не пользуюсь:

repeat(5) do
  puts "Hi!"
end

Теперь следующая фича — можно совать функции в самые неожиданные места. Раньше я просто говорил, что так можно. Но гляньте на вот такой repeat:

class Fixnum
  def repeat(&block)
    1.upto(self) do
      yield
    end
  end
end

5.repeat { puts "Hi!" }

Разница? Да никакой! Ну… хорошо, почти никакой, число повторов почему-то оказалось сзади. Потому что функцию повтора мы вшили прямо в число. В число?!

Подождите, давайте поднимем градус и посмотрим, какие методы там есть. Там есть +! Бугага, давайте его переопределим:

class Fixnum
  def +(rhs)
    self.to_s + " + " + rhs.to_s
  end
end

puts 3 + 2

А теперь о том, почему так делать лучше не надо. Если вы введёте это в irb (interactive Ruby, интерпретатор, выводящий результат каждой введённой строки), он рухнет. Потому что он использует + внутри себя, и у него разрыв шаблона оттого, что + вернул строку. Но вам ничто не мешает определить свой класс, в котором реализовать плюс так, как вы хотите. И не только плюс. Вообще что можно переопределить? Умножение, деление, обращение а-ля массив (a[i]), что хотите, лишь бы остальной ваш Ruby при этом не сломал всю программу — старайтесь следовать “семантике” (смыслу) методов, следующей из их названий.

В общем, Ruby чем-то похож на Linux: сильно зависит от сообщества, имеет обширную стандартную библиотеку и огромное множество библиотек дополнительных, всё это управляется мощным “менеджером пакетов”. Да и гляньте на поведение — он как Linux, делает всё, что ему скажут, к каким бы страшным последствиям это ни привело.

Я уже потерял счёт того, сколько дней этот пост пишу. Соотношение написано/опубликовано сейчас где-то около трёх. Будем считать, что тема раскрыта. Так это, или нет, без обратной связи не узнать. Дальше я буду рассказывать уже не о Ruby, а о Rails, который принёс Ruby популярность.