В последнее время Ruby, что называется «на
слуху». Однако, не смотря на то, что в
Интернете есть достаточно много материала
по этой теме, разобраться с тем, что же собою
представляет Ruby, в чем его преимущества и в
том, как и где его использовать не совсем
просто. В этой статье я попытаюсь не только
рассказать о языке Ruby, но и о технологиях,
связанных с ним. Я позволю себе снабдить
этот обзор некоторыми комментариями,
которые, возможно, покажутся вам
субъективными. Тем не менее, надеюсь, что
они помогут понять причины такой
популярности нового языка и оценить,
насколько же эффективно его можно
использовать в собственных разработках.
Прежде всего, во избежание путаницы,
давайте разберемся с терминологией. Что
такое
Ruby? В чистом виде, это просто язык
программирования. Кроме того, это
транслятор кода (ruby.exe, если мы говорим о
версии для Win32).
Ruby on Rails – платформа (framework) для
разработки Web приложений на базе Ruby.
FreeRIDE и SciTE - IDE и редактор кода для Ruby.
Инсталляция и начало работы
На сегодняшний день последней версией Ruby
является 1.8.4. С сайта http://www.ruby-lang.org/
можно скачать интерпретатор Ruby и
библиотеки. Можно так же скачать FreeRIDE и SciTE.
К слову, если вы не можете скачать несколько
десятков мегабайт – не расстраивайтесь.
Все это входит в комплект Ruby on Rails, который
публиковался
в Спеце, посвященном
Web.
Для запуска Ruby программы достаточно
набрать ruby < имя файла программы>.
Естественно при этом не стоит забывать о
путях. Если же вы хотите воспользоваться
FreeRIDE под Windows, то сразу включите опцию "Run
process in terminal" в Debugger Preferences. Конечно, FreeRIDE не
слишком функциональная среда разработки,
но, тем не менее, минимальный набор
необходимых функций она обеспечивает.
Помимо редактора, со стандартным набором
функций и подсветкой синтаксиса
присутствует дебаггер, а также
автоматизирован запуск и остановка
программ. Что еще надо для отладки скриптов?
Итак, запускаем первую программу:
puts 'hello world'
Жмем F5, открывается так называемое
терминальное окно, в котором и отображается
искомая фраза. Не забывайте, что вы имеете
дело с транслятором, поэтому перед запуском
программы модифицированный код
обязательно нужно сохранить в файл. Я
попробовал сразу поставить птичку «save files
before running/debug» (меню «edit/ Preferences… /Debugger/Run»), но
ожидаемого эффекта это не дало. Такие
мелкие багги, конечно, имеют место в IDE.
Я позволю себе маленькое лирическое
отступление. За несколько дней, до того, как
приступить к написанию данного обзора я
общался со своим коллегой на предмет
сравнения языков и принципиальной
возможности и целесообразности «отсечения
тупиковых путей развития языков».
Соответственно, мы затронули и вопрос «строгости»
языков. Нужно ли четко определять типы
переменных, нужно ли обязательно вручную
высвобождать объекты и т.д. И вот здесь была
озвучена прекрасная, на мой взгляд, мысль.
Отсутствие строгости в языках – прекрасно,
более того, оно предоставляет кодерам
некоторые замечательные возможности и в
значительной мере упрощает их работу. Но
все это справедливо тогда и только тогда,
когда программист четко представляет, как
это работает и к каким последствиям это
может привести. В противном случае это
может сильно навредить разработчику и не
только затруднить процесс отладки
программы, но и привести к неявным ошибкам в
алгоритмах, которые очень трудно отловить.
Поэтому, возможно, ответ на часто
задаваемый вопрос о том, какой язык стоит
начинать учить – не столь очевиден. Здесь
следует понимать, что имеет в виду
вопрошающий. Или он знает основы
программирования и пытается выбрать для
себя среду разработки для практической
работы с перспективой на будущее. Или он
просто хочет научиться программировать «с
нуля».
Теперь давайте посмотрим, что собою
представляет Ruby, с точки зрения
вышесказанного. Сразу скажу, что Ruby не
является «Си-образным» языком. Создатели
языка называют это преимуществом. Мне же
синтаксис Ruby напомнил одновременно
синтаксис Бейсика и Паскаля. Здесь имеют
место конструкции «BEGIN … END», «IF … THEN ..» и т.д.
Но есть некоторые особенности синтаксиса,
которых лично я не встречал нигде.
Прежде всего, сразу скажу, что этот язык
чувствителен к регистру. Но не в
классическом понимании, как в С, где нельзя
использовать переменную MyVar вместо myvar. Имя
переменной всегда должно начинаться с
маленькой латинской буквы (например myVar).
Если же мы напишем в коде MyVar, то это уже
будет константа. Весьма интересно, не
правда ли? Возможно, у вас возникнет вопрос:
«А зачем?». Ответ очевиден. Ruby –
транслируемый язык, и компилятор не сможет
вам указать на ошибку при попытке присвоить
константе новое значение. А когда вы
наглядно видите отличие между константой и
переменной (даже в чужом коде), вы постоянно
будете начеку. Как по мне – весьма удобно.
Переменные в Ruby объявлять не надо. При
первом вызове Ruby сам догадается о типе
переменной. Второй пример более интересный:
print('Enter your name: ' )
name = gets()
puts( "Hello #{name}" )
Здесь программа спрашивает имя пользователя и после его ввода выводит его на экран вместе со словом «Hello». Обратите внимание на то, как передается значение переменной name. Оно как бы врезано непосредственно в строку, при этом признаком переменной является набор символов #{…}. Эквивалентным выражением будет и следующая строка
puts( "Hello " + name). Вместо двойных кавычек можно использовать одинарные, но при этом «врезать» значение переменной уже не удастся.
С тем же успехом можно подставлять значения не только строковых переменных, используя #{…}. Результатом следующего выражения будет вывод на экран числа 9.
puts( "#{(1+2) * 3}" )
Отличие print от puts в том, что в последнем случае происходит перевод строки. Перевод строки можно так же реализовать и с помощью директивы \n, а вставку табуляции - \t. Но это будет работать так же только тогда, когда используется двойная кавычка. Эквивалентом puts('Enter your name: ' ) будет print(“Enter your name:\n” ).
К слову, круглые скобки – опциональны. Выражение puts "#{(1+2) * 3}" так же будет работать.
Конечно, малоприятным моментом является искажение русской кодировки при работе со строками. Скажем, результатом выполнения следующей строки кода будет некая шифрограмма:
print(‘Введите ваше имя: ' )
Я попробовал запустить эту программу в FARe, который спокойно «разговаривает» по-русски, но получил тот же результат. В документации присутствует небольшой раздел, посвященный кодировке, но конкретных рецептов там не приводится.
Объектно-ориентированная концепция Ruby
Конечно, в рамках обзора невозможно полностью описать весь синтаксис языка. Но я не случайно начал с обзора работы со строками. Это связано с тем, как позиционируется язык. Но давайте теперь рассмотрим еще один пример, который продемонстрирует, как происходит преобразование типов в Ruby.
taxrate = 0.175
print "Enter price (ex tax): "
s = gets
subtotal = s.to_f
tax = subtotal * taxrate
puts "vat on $#{subtotal} is $#{tax}, so grand total is $#{subtotal+tax}"
Смысл кода, приведенного выше – очевиден. Считается сумма с налогом. Но обратите внимание на строчку
subtotal = s.to_f.
Несложно догадаться, что to_f это метод объекта s. Следовательно, становится очевидным, что любой тем данных в Ruby – класс, а любая переменная – объект, или, если хотите, экземпляр класса. Это очень похоже на концепцию .Net вообще и C# в частности. Более того, если анализировать следующий фрагмент кода x = 1 + 2, то + будет методом, а 2, соответственно, передаваемым ему параметром. С точки зрения концепции языка логичнее было бы написать x=1.+(2) . Мне захотелось проверить это предположение, и я ввел в редакторе кода следующее выражение
puts(1.+(2))
Представьте мое удивление, когда в результате выполнения программы на экране высветилась тройка. На самом деле в документации по Ruby позиционируются основные типы данных. Но нужно четко понимать, что на самом деле это классы.
Здесь я не могу не вспомнить одного из интервью David’а I, в котором он сказал, что мечтает реализовать IDE для Ruby в рамках BDS. Лично мне совершенно понятна его заинтересованность в этом. Объектно-ориентированный подход дает массу технологических преимуществ, позволяющих создавать качественные и функциональные IDE продукты и, тем самым, повышать скорость разработки конечных решений. Достаточно вспомнить, что MDA реализация от Borland получила перспективы лишь с переходом на .Net. Учитывая примитивность FreeRide (как же хочется увидеть в редакторе, после нажатия точки, выпадающий список всех методов), у DevCo есть прекрасная перспектива создать средство разработки, которое может стать реальным конкурентом ASP .Net в своем сегменте рынка.
И еще одна маленькая ремарка. В своей предыдущей статье я делал краткий
обзор нового языка
D. И автор этого языка Уолтер Брайт высказывал мысль о том, что объекты уместны лишь там, где это действительно нужно. В Ruby реализован другой принцип – объектами является все. Я не буду судить у преимуществах каждого из этих подходов. На мой взгляд, основной вопрос заключается в том, как правильно этим распорядиться.
Теперь давайте посмотрим, как в Ruby создаются классы. Но прежде немного определимся с терминологией.
Декларация класса начинается со слова class (нижний регистр), затем следует его имя (начинаться должно с верхнего регистра). Заканчивается декларация класса ключевым словом
end.
class Magazine
def set_mag(aName)
@myname=aName
end
def get_mag
return(@myname)
end
end
В теле класса определены два метода set_mag и get_mag. Следующий фрагмент кода проиллюстрирует, как создается экземпляр класса.
likedMagazine = Magazine.new
likedMagazine.set_mag('Xakep')
puts(likedMagazine.get_mag)
Обратите внимание на переменную @myname, используемую внутри методов. Знак «@» перед именем переменной означает, что она определена для каждого экземпляра класса и не может вызываться вне класса. Своеобразный способ задать область видимости или, если угодно, реализовать инкапсуляцию.
Кстати, слово return в методе get_mag – опционально. На самом деле, Ruby в качестве возвращаемого значения возьмет последнее выражение.
То, что Ruby полностью объектно-ориентированный язык доказывает следующая строка.
puts self.class
В качестве результата будет выведено слово Object.
puts self выдаст – main. Обратите внимание на то, что Object с синтаксической точки зрения – константа.
На самом деле все создаваемые в Ruby классы наследуются от класса Object. Среди методов, описанных в этом классе, есть такой как to_s, определяющий строковое представление класса. Что бы проверить это, достаточно в последнем примере дописать еще одну строку
puts likedMagazine.to_s
Программа выдаст вполне корректный, хотя и малоосмысленный результат.
Классы в Ruby никогда не бывают закрытыми. В существующий класс всегда можно добавить метод. Это работает как для стандартных классов, так и для «самописных». Иными словами мы всегда можем переопределить метод стандартного класса в наследнике.
class Magazine
def set_mag(aName)
@myname=aName
end
def get_mag
return(@myname)
end
def to_s
@myname
end
end
likedMagazine = Magazine.new
likedMagazine.set_mag('Xakep')
puts likedMagazine.to_s
В приведенном выше коде показано переопределение метода to_s. Теперь результатом выполнения программы буде не внутренний идентификатор объекта, а название (точнее, значение внутренней переменной myname) журнала.
Еще один метод, описанный в классе Object – initialize. Это специальный метод, который вызывается при создании объекта. Он может оказаться очень удобным для задания начальных значений переменных создаваемого объекта.
Теперь давайте попробуем разобраться с наследованием в Ruby. Естественно, что эта концептуальная идея ООП так же реализована. Вот пример реализации:
class Magazine
def set_mag(aName)
@myname=aName
end
def get_mag
return(@myname)
end
def to_s
@myname
end
end
class FavMag < Magazine
def get_mag
' My favorit magazine is'+@myname
end
end
likedMagazine = FavMag.new
likedMagazine.set_mag('Xakep')
puts likedMagazine.get_mag
Символ < обозначает, что описываемый класс является наследником того класса, имя которого следует за этим символом.
Ruby на прямую не поддерживает множественного наследования, т.е. у класса может быть непосредственно только один родитель. Однако в Ruby классы можно подключать модули и, соответственно, отдельные методы модуля могут вызываться как отдельные модули класса. Эта возможность носит название Mixin. Но прежде всего давайте разберемся с понятием модуль. Модули в Ruby очень напоминают паскалевские
(unit).
module Trig
PI = 3.141592654
def Trig.sin(x)
# .. , кстати, комментарий в Ruby - #
end
def Trig.cos(x)
# ..
end
end
Выше приведен пример описания модуля. Подключение внешних модулей (т.е. тех модулей, которые размещены в другом файле) выполняется с помощью ключевого слова require.
require "trig"
А вызов производится с использованием точечной нотации.
y = Trig.sin(Trig::PI/4)
Точка используется перед вызовом метода, а двойное двоеточие – перед обращением к константе.
Таким образом, модули могут объединять в себе константы, классы и методы. Тем не менее, модуль – не более чем пространство имен. В одном файле может быть размещено несколько модулей.
Когда подключается модуль, Ruby создает безымянный класс, который ссылается на этот модуль и подставляет его как отдельный суперкласс для класса, в который он подключается. Немного запутанно, но, тем не менее, на практике все выглядит достаточно просто.
В класс можно подключать несколько модулей одновременно. Однако, нужно следить за тем, что бы имена методов внутри модулей не пересекались.
Для того, что бы создать более целостное представление о Ruby как о языке, я бегло затрону остальные его синтаксические элементы.
Массивы в Ruby, в отличии от большинства языков могут объединять в себе данные разных типов. При этом в качестве элемента массива может использоваться даже метод некоторого класса, при условии, что он возвращает какое-либо значение.
a1 = [1,'two', 3.0, array_length( a0 ) ]
Нумерация элементов массива начинается с 0.
Обращение к элементу массива по его индексу можно осуществлять следующим образом:
puts (a1[0])
Кроме того, можно обращаться и по диапазону элементов. О том, что диапазон «вылетает» за размерность массива беспокоиться не следует. Иными словами будут работать оба выражения:
arr = ['X','a','k','e','p']
print( arr[0,4] )
print( arr[0,5] )
Для создания массива достаточно его просто определить в коде. Но, не стоит забывать, что массив, как и все остальное в Ruby, это класс. Поэтому можно использовать и такой синтаксис.
a= Array.new([2,3,4])
Для того, что бы создать многомерный массив нужно просто создать один массив и добавить другие массивы в каждый его элемент.
a = Array.new(2)
a[0]= Array.new(2,'hello')
a[1]= Array.new(2,'world')
Оператор ветвления задается конструкцией if … then … end. Например:
if (subtotal < 0.0) then subtotal = 0.0 end
Логично, что между then и end может ставиться else.
Если сравнивать с Паскалем, то объективно, такая конструкция немного удобнее, ведь тогда не имеет смысла использовать блоки. Блоки, к слову задаются конструкцией
begin … end
Противоположностью if является конструкция unless (если не):
unless aDay == 'Saturday' or aDay == 'Sunday'
daytype = 'weekday'
else
daytype = 'weekend'
end
Как и в большинстве других языков в Ruby есть несколько вариантов организации циклов, каждый из которых удобен в разных конкретных случаях. Две конструкции цикла приведены в следующем примере. Думаю, комментарии излишни.
# for
for x in [1, "two", [3,4,5] ] do puts( x ) end
# each
[1, "two", [3,4,5] ].each do |x| puts( x ) end
К слову, в последней версии Delphi Language также введена конструкция for … in, именно с целью облегчить работу с классами.
А таким образом работает цикл While:
a=0
begin
puts a
a=a+1
end while a<5
Так же в Ruby можно использовать цикл until:
i = 10
until i == 10 do puts(i) end # never executes
until i == 10 # never executes
puts(i)
i += 1
end
Естественно, что в рамках одной статьи невозможно (да и не нужно) рассказать обо всех особенностях синтаксиса языка. Но надеюсь, что общее представление вы смогли себе составить. В процессе отладки примеров мне действительно показалось, что, несмотря на простоту, Ruby является достаточно мощным языком. Тем не менее, возможно, это суждение не совсем объективно. Все же тестовые примеры это не реальные приложения.
В заключение хочу сказать, что все вышеизложенное не абстракция. На Ruby разрабатываются реальные Веб-приложения. Во второй части я попытаюсь рассказать о практической стороне Ruby, а собственно о том, как создаются приложения на платформе Ruby on Rails. Думаю, что преимущества и недостатки синтаксиса Ruby станут более очевидными.