D:\sideБлогRuby — стоит ли?

Жил да был когда-то человек, называвший себя в интернете Matz, и вывели его как-то раз разные языки программирования из себя настолько, что он решил создать свой язык, с чаем и плюшками. Так появился Ruby. И никто бы о нём не узнал, если бы не нашумевший фреймворк Rails, написанный на этом языке. Как ни странно, эти вещи появились почти независимо друг от друга: язык разработал один человек, а взрыв популярности ему обеспечил совсем другой. Зададимся вопросом — стоит ли язык изучения, даже если вы не планируете строить веб-приложения?

Коротко говоря, мой вердикт — да, стоит. Во многом потому, что это просто интересный язык, в котором элегантно применили множество идей, имеющихся ныне во многих разных языках. То есть, у языка нет существенных преимуществ перед такими гигантами, как C++, Java и C#, если говорить о возможностях. Но если говорить об удобстве разработки — Ruby способен обойти многие из этих языков в большинстве задач. Цена всему этому — интерпретируемость. Это интерпретируемый язык, и поэтому не стоит ожидать от него космических скоростей. Годится он, таким образом, для обучения и решения задач, не требующих скорости. Если вы хотите проверить, работает ли спланированный алгоритм, хотите собрать болванку-отвечалку для вашего сетевого приложения (с целью проверки или отладки) — это ваш выбор.

На данный момент я разобрал учебник Майкла Хартла по Rails и заканчиваю книгу «Beginning Ruby» Питера Купера, а также собрал пару работающих поделок на Ruby и Rails. И я впечатлён. В основном я впечатлён аккуратностью, с которой можно писать программы на Ruby. Во многом мне тут помогает Light Table, обеспечивая правильные отступы, если я потерял нить происходящего. Это же частенько позволяет находить ошибки синтаксиса — я частенько забываю закрывать блоки по одной команде, считая это излишеством после C и GML.

Итак, первая забавная особенность, обычная для интерпретируемых языков — reflection, возможность получать информацию о стуктурах работающей программы в виде, пригодном для обработки этой же программой. Разумеется, у reflection есть смысл только в объектно-ориентированных языках, поскольку только в них исполняемый код структурируется настолько сильно, чтобы пользоваться им было полезно. Скажем, взяв произвольный объект, вы можете быстро узнать, какие методы у него есть:

2.1.0 :003 > object.methods.join(' ')
 => "nil? === =~ !~ eql? hash <=> class singleton_class clone dup taint tainted? untaint untrust untrusted? trust freeze frozen? to_s inspect methods singleton_methods protected_methods private_methods public_methods instance_variables instance_variable_get instance_variable_set instance_variable_defined? remove_instance_variable instance_of? kind_of? is_a? tap send public_send respond_to? extend display method public_method singleton_method define_singleton_method object_id to_enum enum_for == equal? ! != instance_eval instance_exec __send__ __id__"

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

У этого есть очень забавные побочные эффекты. И под «забавными» я понимаю класс шуток, пригодных на 1 апреля (которое, кстати, настанет совсем скоро). Вот пример. Какая длина у слова "Hello"? 20. Инфа 100%. irb (интерактивный Ruby) подтвердит:

2.1.0 :001 > class String
2.1.0 :002?>   def length
2.1.0 :003?>     20
2.1.0 :004?>   end
2.1.0 :005?> end
 => :length 
2.1.0 :006 > "Hello".length # Смотрите сами!
 => 20 

Счастливой отладки!

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

Вторая особенность Ruby — наличие гигантского набора библиотек (просто откройте RubyGems) для широчайшего набора задач. Насколько широчайшего? Есть библиотека, позволяющая встраивать в Ruby кусочки компилируемых языков, вроде С или С++. Есть библиотеки для обработки разметки, вроде Markdown, этот пост отформатирован библиотекой redcarpet. Есть библиотеки для работы с сетью, с которыми можно в десяток строк написать простенький клиент (или сервер?) для протокола UDP. Это, в сочетании с широким кругом разработчиков, называется «экосистемой Ruby». Она во многом базируется на философии «ПО с открытым кодом», отсюда и места базирования — например, Github. «Решил сам, помоги другим — возможно, твоё решение нужно не только тебе, и могут найтись желающие его улучшить». Это, во многом, следствие возникновения Rails, но этим не объясняются обёртки для Ruby таких штук, как Qt и wxWidgets, с помощью которых можно собирать приличные программки с обычными для вашей ОС элементами управления. Списки, галочки, кнопочки, окошки, сообщения. Не вебом единым. Но я увлёкся. Просто я пытался вникнуть в то, что из этого мне реально может быть полезно, и я быстро утонул в разнообразии.

Третья особенность Ruby — изменяемый синтаксис. В сущности, от вас требуется заключение команд в блоки, что полезно почти для всех языков. Почти всё остальное вы можете написать сами, создав собственный язык. Занимается ли этим кто-либо всерьёз? Да сплошь и рядом! Есть такая штука, DSL, в вольном переводе «узкоспециализированный язык». Здесь пример кода библиотеки Whenever объяснит лучше меня:

every 3.hours do
  runner "MyModel.some_process"
end

Немного подробностей о том, что происходит. hours — метод встроенного класса «число» (Fixnum), и он возвращает указанное в часах время в более подходящих здесь единицах измерения, в секундах. every - функция, принимающая момент времени или интервал, а также блок кода, который она в соответствующее время (или с указанным интервалом) исполняет. Очень удобно и хорошо читается. Но посмотрим на более «приземлённый» пример. Вы же знаете, насколько тяжело многим даются циклы в языках программирования. Скажем, вам надо сделать 5 шагов с числами 1-5 внутри цикла. Тёртые программисты это могут сделать не задумываясь. А в Ruby — даже не сомневаясь, что это будет работать именно так:

1.upto(5) do |i| # считая, что "номер шага" в i
  действие_с i   # сделать с ним действие
end

Это «новое поколение циклов», грубо говоря. Зовётся «итератор» и активно используется в известных мне ООП-шных языках: точно в Java, C++ и C#. Ах да, почему это работает? upto — метод класса «число», который исполняет данный ему блок кода, увеличивая каждый шаг данное число на 1, пока оно не станет больше 5. В итоге и выходит, что действие_с i выполнится, при i равном 1, 2, 3, 4 и 5. Пугающе просто, наверное. Но не ново. Это, как говорится, «синтаксический сахар». Во многих других языках подобное тоже можно провернуть, но настолько крутой и короткий синтаксис для этого действа не встретить почти нигде. Причём он короткий с обеих сторон — и использование, и реализация upto довольно просто записываются.

Четвёртая особенность… опишу кратко. Название переменной напрямую определяет о ней некоторые факты. «Мягко». К примеру, Переменная (с большой буквы) считается константой. Из-за большой первой буквы названия, да. И вы будете смеяться, но эту «константу» можно будет менять! Только Ruby это не понравится, и он выведет предупреждение о том, что вы несёте чушь. Тем не менее, он сделает, что вы просите.

На этом, пожалуй, свернусь. Дело в том, что я до сих пор дочитываю вторую книжку по Ruby, и я хочу ещё. Напоследок отмечу, что с Ruby хорошо работать в Linux, там проще всего добиться работы большинства библиотек, взять хотя бы RubyInline, который позволяет писать части программы на C. Дистрибутивы Linux обычно больше предрасположены к готовности собирать нужные программы из исходного кода, и компилятор в дистрибутивах обычно уже есть. К тому же, есть немало особенностей, которых в Windows просто нет из-за неприязни к POSIX: например, fork, позволяющего одному процессу разделиться в два. Из Ruby это можно сделать.

…но, конечно, для этого уже сделали специальную библиотеку. Это можно сказать почти обо всех задачах, что типично возникают. Да, экосистема настолько обширна, поэтому я в ней и потерялся. Но это непередаваемое ощущение, когда язык хоть и медленно (и то не факт!), но делает всё, что я скажу… многие люди из-за этого ощущения и подались в программисты. Для меня же это ощущение оказалось приятным бонусом к принятому решению.