Обзоры
Такой простой японский язык… Ruby
15

Такой простой японский язык… Ruby

Предпосылкой, в очередной раз заставившей автора отвлечь внимание читателя
от дел повседневных и нужных, стал… случайный поход "за книгами".
Пестрое разнообразие всеформатных изданий, заманчиво обещающих потенциальному
обладателю чуть ли не волшебное превращение в программиста, владеющего объектно-ориентированной
методологией проектирования и специализирующегося на самом востребованном языке
C++ (и все это, естественно, в самые немыслимо короткие сроки), буквально за несколько
минут превратилось в… утомительное однообразие.

Слово в защиту Слова

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

Дж. Свифт

Почему речь пойдет о языке, тем более о языке программирования? Ведь все мы убеждены, что "копошение" в языках программирования и прочих инструментальных средствах — удел узких специалистов. Но давайте не будем забывать, что убеждения — это также замечательная питательная почва для заблуждений. И одно из них — игнорирование роли языка в общении с машиной. Почему-то мы не считаем зазорным непомерно долго (практически всю жизнь) учить родной и иностранные языки — скорее всего, потому, что нам нужны по-настоящему удобные, выразительные и гибкие интерфейсы. И, слава Богу, реализация "дружественности интерфейса" пока достигла межчеловеческого уровня только у порожденных искрометными фантазиями Джонатана Свифта бальнибарбийцев. Их меню, пиктограммам и объектно-ориентированному мышлению мешало только слабое развитие полиграфической и электронной промышленности, вынуждавшее носить все множество интерфейсных объектов в мешке за плечами. "Впрочем, для коротких и несложных разговоров можно носить все необходимое в кармане или под мышкой…" — похоже, что и "мобильных пользователей" Бальнибарбии великий Свифт не обделил вниманием.

Но шутки шутками, а языковый способ общения с машиной остается (и, наверное, будет всегда) самым гибким, удобным, лаконичным и выразительным. И, что самое главное, — аналогичным естественному способу человеческого общения, а значит, позволяющим хоть чуть-чуть приблизиться к выполнению Предначертания "Человек создан для творчества…". Если бы это было не так, автору статьи пришлось бы все прошедшие две недели паковать огромный мешок, искать способы доставить неподъемный груз на арену амфитеатра, вмещающего десятки тысяч "читателей" (простите, "смотрителей") и начать свое "выступление" (точнее, "показание") с извлечения из недр мешка ( в других терминах — из "объектно-ориентированной статьи с дружественным пользовательским интерфейсом") исключительно красивого и прекрасно ограненного драгоценного камня — рубина. Возможно, такая "user friendly" техническая журналистика "по Свифту" кому-то и понравится, но все же… давайте просто прислушаемся к звуку слова Ruby.

Шутки истории…

Человек создан для творчества, и я всегда знал, что люблю
создавать вещи. Увы, я обделен талантом художника или музыканта.
Но я могу писать программы.

Йокихиро Мацумото (Matz)

На постсоветском пространстве уже достаточно много лет действует одно непреложное правило: если вы служили в армии или даже если вы просто мужчина, то каждый год 23 февраля вы отмечаете праздник — как показала практика, независимо от социально-политической системы и текущего названия государства. Ну а если вы полюбили Ruby всей душой, то 23 февраля вы будете отмечать сразу два праздника — ведь это еще и День Рождения Ruby.

Именно в этот день в далеком 1993 г. талантливый японский программист Йокихиро Мацумото (известный в программистских кругах под сокращенным именем Matz), беседуя с коллегами, выяснил для себя недостатки существовавших на то время языков "общения" с компьютером (которые почему-то мы называем языками программирования) и сформировал облик нового, удобного и более совершенного языка. Дальнейшие события развивались быстро (как скромно вспоминает сам Matz): "So, I decided to make it" ("Поэтому я решил создать его").

Несколько месяцев Matz затратил на разработку первого интерпретатора, организация же библиотеки классов отняла намного больше времени — первая публичная версия Ruby (с номером 0.95) увидела свет в конце 1995 г.

На продвижение "в третьем знаке" (изменение на 0.05 в номере) — версию 1.0, ушел год, во "втором" — еще годы. Сегодня Ruby версии 1.6.3 насчитывает "за плечами" более восьми лет истории. Так что даже если вы панически боитесь "сырых" программ, к Ruby можете относиться безбоязненно — это очень стабильная и динамично развивающаяся разработка. К слову, на своей родине, в Японии, Ruby фактически вытеснил из списка симпатий своих ближайших, но более "великовозрастных" конкурентов — Perl и Python.

И серьезность идей

Я хочу, чтобы компьютер был моим слугой, а не господином,
поэтому я должен уметь быстро и эффективно объяснить ему, что делать.

Matz

На Йокихиро Мацумото (по его собственному признанию) в свое время оказала огромное влияние ставшая уже знаменитой книга Ларри Уолла (Larry Wall), автора еще одного замечательного языка — Perl. Лучшие идеи Perl, лаконично и талантливо сформулированные Уоллом, были положены Matz в основу Ruby: "Решения простых задач должны быть простыми, решения сложных задач — возможными. Способ решения не должен быть одним". Кроме того, Matz использовал практически все лучшее, что было реализовано во множестве принципиально разных, но доказавших свое высочайшее качество популярностью и возрастом, языков программирования — CLU, Eiffel, Smalltalk, Lisp, C, Ada. Возможно, в этом перечне кто-то усмотрит обреченную на провал попытку объединить несовместимое, — не торопитесь. "Гибрид" оказался не просто живучим, но и поражающим изящностью, и, если так можно выразиться, "прозрачностью" конструкций. В чем (в рамках возможностей журнальной статьи) нам предстоит убедиться.

Одни объекты

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


И это все о Ruby: мудрая
простота
дзен-буддизма и красота…

Увы, еще до этапа практического освоения начинающих ожидают тяжелые испытания
— ведь поговорка гласит: "сколько есть программистов, столько и определений
объектной ориентации". Практика впоследствии не оставит от доверчивости и
следа — слишком многого и важного не договаривают (или не доделывают, чаще —
и то и другое) разработчики объектно-ориентированных языков и технологий, вынуждая
программистов ликвидировать огрехи дизайна на самых поздних стадиях разработки
— как раз тогда, когда это превращается в очень дорогое и трудное занятие.

Уже многократно повторенный термин "объектная ориентация", естественно, употреблен в статье не случайно, — предмет нашего обсуждения — язык Ruby, является представителем не слишком обширного класса "чистых объектно-ориентированных языков". "Чистота" здесь подразумевает, что практически все понятия, которыми позволяет оперировать язык, являются объектами. Почему же это хорошо и, вообще, зачем это нужно?

Каноническая книга Н. Вирта "Программы = Алгоритмы + Структуры данных" самим названием отражает устоявшуюся и прекрасно отработанную методологию проектирования программ, иначе именуемую "структурное программирование". В ее рамках данные — это нечто пассивное, обладающее минимальным набором характеристик — положением и типом. Положение позволяет "добраться" к нужным данным (например, с помощью уникального имени для них), а "тип" определяет перечень операций, которые над данными можно производить. Сами же операции — множество алгоритмов, существующих совершенно отдельно, "вне" данных.

Надо сразу сказать, что и структурное программирование, несмотря на преклонный возраст, работает, и работает очень хорошо. Но… до определенных пределов сложности. В терминах наших рассуждений "сложность" означает разнообразие и количество типов данных, их положений и, наконец, алгоритмов. С ростом этого разнообразия программу становится все труднее понимать и, следовательно, совершенствовать (изменять). А утратив способность к совершенствованию, программа, как и живой организм, умирает.

От такой "хронической болезни с летальным исходом" лекарства искали, находили и продолжают находить.

Один из вариантов — пресловутая объектная ориентация — подразумевает придание самим данным… поведенческих свойств с помощью "упрятывания" данных за алгоритмами, которые обеспечивают их обработку. В результате такой относительно нехитрой "перестройки мышления" данные становятся активными, что часто (или по идее часто — на самом деле не все так просто) облегчает понимание, создание программ и нахождение в них ошибок. Последний пункт особенно важен — если в большой программе, созданной на основе структурного программирования, данные типа "год" неожиданно для разработчика приняли отрицательное значение, поиск ошибки способен превратиться в кромешный ад — для него может потребоваться детальное изучение всей программы. Активность же данных типа "год", означающая возможность выполнения операций над ними только с помощью соответствующих алгоритмов, позволяет с очень высокой степенью уверенности говорить — ошибка кроется именно в этих алгоритмах.

Для описания активных данных, именуемых объектами, разработчики используют разные способы, предоставляемые языками программирования. И многие неприятности, мягко названные выше "недоговорками" и "недоделками", начинаются с механизмов описания объектов. В некоторых языках для этого применяются "штампы", существующие исключительно до момента превращения текста программы в исполняющуюся задачу. Такие "штампы" обычно называют классами, потому что они используются для "штамповки" множества объектов, обладающих одинаковыми поведенческими свойствами и различающихся только значениями данных и положением (иначе говоря, принадлежащих к одному классу). Такой подход, несмотря на относительную простоту реализации и высокие показатели быстродействия созданных программ, обладает одной неприятной особенностью — реальность часто требует возможности описания различий между объектами одного класса не только в значениях данных, но и в поведении. Увы, при "штамповочном" подходе описания классов для реализации такой возможности необходимо или порождение лишних сущностей — новых классов, или разнообразные ухищрения, зачастую сводящие на нет все достоинства методологии ОО-проектирования.

Чистые ОО-языки (например, Smalltalk и Ruby) лишены этих недостатков — в них класс сам по себе является некоторым объектом и существует не только "на бумаге", но и в процессе выполнения задачи.

Блеск рубина

Традиции требуют начинать знакомство с любым языком программирования со знаменитой
"программы" "Hello, World" ("Здравствуй, Мир"),
действия которой примитивны — она просто должна отобразить на устройстве вывода
приведенную фразу. Мы эти традиции нарушим и смело начнем с вещи куда более информативной,
а именно, с тривиальной, несложной, но все-таки весьма необычной по возможностям
программы расчета факториала:

1 def factor( chislo )
2 if chislo == 0
3 1
4 else
5 chislo * factor( chislo-1 )
6 end
7 end
8 print factor(ARGV[0].to_i)


Несмотря на краткость и "прозрачность", factor приоткрывает некоторые
важные особенности Ruby, в первую очередь, отсутствие в программе каких-либо сведений
о типе единственной используемой переменной с именем chislo. И действительно,
в Ruby все переменные играют роль контейнеров, где могут размещаться объекты любых
типов, а сведения о типе объекта хранятся в самом объекте. Если, еще не разбираясь
с реализацией, мы попробуем выполнить эту программу с помощью интерпретатора ruby
и попытаемся найти факториал, например числа 7000 (предположительно цифра должна
быть астрономического масштаба), Ruby заставит ненадолго "задуматься"
ваш компьютер и все-таки выдаст результат — целое число из… 23879 цифр! Вот
и еще одна особенность, далеко не главная, но приятная, Ruby приоткрыла завесу
тайны — в нем реализована работа с целыми числами фактически произвольной разрядности
(в большинстве языков реализация целого числа "привязана" к разрядности
процессора, что не позволяет на 32-битовых компьютерах без ухищрений оперировать
целыми числами больше 232). Кроме того, такая весьма ресурсоемкая задача, как
вычисление факториала, работает на Ruby достаточно быстро, что свидетельствует
в пользу эффективности реализации.


Официальная символика Ruby

пока не выбрана, она ждет своих авторов

Теперь взглянем пристальнее на текст нашей первой программы и попробуем разобрать
его "построчно". Первая строка создает объявление (def — define —
"объявить") новой функции с именем factor, принимающей в качестве параметра
некоторый "контейнер"-переменную с именем chislo. Тело программы записано
начиная со второй строки и до строки с номером 7 и содержит фактически один оператор
if — else. Здесь мы можем наблюдать еще одну интересную особенность Ruby — в
этом языке каждый оператор всегда возвращает значение, и в построении программы
factor использована важная идиома Ruby — ведь вся программа работает за счет
возврата значения оператором if — else. Попробуем разобраться: например, если
значение данных объекта, хранящегося в переменной chislo, равно 0, то значение
оператора if — else становится равным 1, и это число возвращается из функции
factor в соответствии с еще одной идиомой Ruby — функция всегда возвращает значение
последнего выполненного в ней оператора.

Строка с номером 8 отвечает за "интерактивность" программы — выдачу результатов командой с очевидным именем print. Но не только — она же отвечает и за ввод данных, и за минимальную защиту всей нашей программы от ошибок пользователя. Если текст данной программы (строки 1—8) сохранен в файле с именем, например, ff.rb и мы запускаем его на выполнение строкой "ruby ff.rb 7000", чтобы получить те самые 23879 цифр результата, то интерпретатор ruby автоматически создаст переменную, содержащую массив строк, с именем ARGV, и в первую строку этого массива (индексы массивов начинаются с 0!) запишет значение "7000". Но ведь это текст, а не число, и здесь помогает всеобщая объектная ориентация Ruby — строки, хранящиеся в массиве, записанном в "контейнере"-переменной с именем ARGV, на самом деле являются полноценными объектами некоторого класса и содержат множество полезных, реализованных прямо в них, алгоритмов. Эти привязанные к типу алгоритмы называются методами, а "добраться" до них можно с помощью селектора — символа "." (назовем его условно "выбрать_и_вызвать_метод"). Например, в нашей программе, ARGV[0].to_i можно прочитать так: "выбрать_и_вызвать_метод" с именем "to_i" для объекта, хранящегося в "контейнере"-переменной ARGV[0]. Если мы заглянем в документацию, то выясним, что метод to_i осуществляет преобразование строки в число (если оно, конечно, допустимо).

Теперь давайте обратим внимание на… имя единственной использованной в функции factor переменной — chislo. На первый взгляд ничем не примечательное, оно все же содержит важную информацию, потому что… начинается с буквы в нижнем регистре. Согласно принятым в Ruby правилам первая буква имени переменной является как бы частью объявления. Так, имена, начинающиеся с букв нижнего регистра (a—z) и символа подчеркивания "_", объявляют так называемые локальные, или автоматические, переменные (они автоматически создаются, когда поток выполнения программы доходит до оператора, содержащего их объявление, и автоматически уничтожаются за пределами этого оператора — в нашем примере таким оператором является сама функция factor). Имена, начинающиеся с символа "$", объявляют глобальные переменные — существующие во все время исполнения программы и доступные из всех ее "подсистем". Согласно классике программирования активное использование глобальных переменных снижает пригодность к пониманию/модификации программы — но в Ruby это немного не так (почему — чуть позже). Для упрятанных в объекты данных существует и свое правило объявления имен — они должны начинаться с символа "@". Если же нужны глобальные переменные в пределах одного класса, т. е. доступные только для объектов этого класса, их именуют начиная с символов "@@". И наконец, имя переменной, начинающееся с символа верхнего регистра, свидетельствует о том, что содержание этой переменной нельзя изменять — это константа.

Идеология Ruby "все—объект", естественно, подразумевает, что "контейнеры"—переменные также должны быть… объектами некоторого класса. И в реализации языка именно так и есть — а это означает очень интересные возможности. Например, к классически "плохим" глобальным переменным можно "привязывать" методы, которые будут вызываться автоматически при изменении их содержимого. Такая возможность, в полном соответствии с правилами Ruby, исполнена очень изящно и позволяет существенно упростить, например, программирование ориентированных на обработку событий программ (к ним, в частности, относятся все графические пользовательские интерфейсы).

Наконец, мы можем забыть о факториалах и бросить короткий взгляд на способности встроенной библиотеки Ruby по работе с объектами разных типов. Как и большинство скриптовых языков, Ruby содержит очень мощную реализацию самого главного (в рамках языковой модели) типа — строки. Строки в Ruby представляются последовательностью символов, заключенной в кавычки двух типов — двойные ("") и одинарные (»). Главное различие между ними — строка, заключенная в двойные кавычки, принимает значение после автоматического скрытого выполнения некоторых операций над ней, одинарные же кавычки фактически означают "что видишь — то и имеешь". Так, если в программе вы напишете строку ‘Ruby is great language for #itc_drupal_me’, ее значением будет то, что вы написали, вне зависимости от контекста программы. Но достаточно изменить кавычки, и, если ваша программа содержит переменную с именем me, в которой записана, например, строка "Andrew", то значением строки будет фраза "Ruby is great language for Andrew". Оператор #itc_drupal_, используемый в данных типа строка, вызывает выполнение заключенных в фигурные скобки операторов и подстановку результата в строку — естественно, когда строка ограничена двойными кавычками. Строки можно "складывать", "умножать", индексировать самыми простыми и очевидными способами:

name = "Andy"
sname = "Z"
1 fname = name + sname
2 fname = name + sname * 3
3 name[0]
4 name[-1]


В случае 1 fname, очевидно, содержит строку "AndyZ", в случае
2 — строку "AndyZZZ". Операторы индексирования (варианты 3 и
4) возвращают соответственно значение "A" (индексация начинается
с 0!) и… "y" — отрицательный индекс не является ошибкой и
указывает, что индексация начинается с конца строки. Увы, коротким знакомством
с этими базовыми операциями со строками мы вынуждены ограничиться — весь перечень
строковых возможностей Ruby слишком необъятен.

Кроме строк и уже упомянутых выше целых чисел, Ruby поддерживает также типы "массив" и "хэш-таблица". Принципиальное отличие между этими двумя типами данных заключается в том, что массив формирует множество пар "численный индекс"—значение, а хэш-таблица — множество пар "индекс некоторого типа"—значение. Общим же остается главная особенность Ruby — контейнерный характер переменных, что обеспечивает хранение, например, в массиве объектов любых типов:

array = [2001, "is", "good",
"year", "to" , ‘learn Ruby’]
hash = itc_drupal_ 1 => 2001, ‘who ?’ => "AndyZ", ‘do ?’ => "like",
‘what ?’ => "Ruby"


Оставленные напоследок ОО-способности Ruby не столько сложны, сколько требуют
определенной подготовки для их освоения. Но все же… В Ruby также есть "штамповочное"
представление класса, записываемое в самой примитивной форме. Например, очень
полезный класс, описывающий "штамп" для создания множества автомобилей,
способных только сигналить, и при этом одинаково:

class Car
def signal
print "би-би"
end
end


Конкретный "автомобиль" — объект создается с помощью штампа традиционно:

Toyota = Car.new


и может "проявлять свои свойства" с помощью активации методов селектором
— на строку Toyota.signal интерпретатор Ruby ответит: "би-би".
Здесь следует только помнить, что "штампы" Ruby на самом деле — объекты
некоторого класса, наследующие от него ряд методов, в том числе и использованный
метод создания нового объекта new. Наследование — одно из ключевых понятий ООП,
означает образование чего-либо нового на основе уже существующего с помощью расширения
и/или подмены свойств. В качестве примера создадим наследованием еще один класс
автомобилей, которые еще и рычат:

class NeSovsemCar < Car
def move
print "дыр-дыр-дыр"
end
end


Факт наследования свойств от класса Car указан в объявлении Trac. Если теперь
создать объект класса Trac, например ГАЗ = NeSovsemCar.new, и посмотреть
на поведение объекта, находящегося в переменной ГАЗ, тем же способом, что и для
Toyota, результат будет вполне предсказуемым: ГАЗ.signal вызывает реакцию
"би-би-би", но при "езде" громко рычит — на строку ГАЗ.move
интерпретатор отреагирует "дыр-дыр-дыр".

Ruby позволяет также переопределять в классах-наследниках некоторые поведенческие свойства (что по-умному именуется полиморфизмом), например так:

class SovsemNeCar < NeSovsemCar
def move
print "дыр… все, сломался"
end
end

Машины этого класса отличаются от своих "предшественников" тем, что
любая попытка заставить их "поехать" (вызвать метод move) приведет к
сообщению "дыр… все, сломался".

Впрочем, все эти ОО-прелести доступны, например, и в Java. Зато настоящие достоинства Ruby — возможность просто расширять функциональность конкретных объектов, а не классов, с помощью объявления так называемых синглетонных методов, очень удачная концепция замены множественного наследования техникой mix-in (смешения), мощная модульная структура, дополняющая объектно-ориентированные возможности, полноценная реализация всех функций (или процедур) как объектов соответствующего класса (следовательно, исполняемые программы приобретают свойства данных — их можно передавать и возвращать в другие программы в качестве параметров и т. д.), отличная реализация итераторов и, что самое главное — доступность и простота освоения всех тонкостей настоящей техники объектно-ориентированного программирования.

Что, где, как?

Не стоит обвинять автора в том, что все настоящие "изюминки" Ruby
статья оставляет "за кадром" — настойчивый и заинтересованный читатель
теперь непременно и быстро "доберется" до них сам. Интерпретатор Ruby
— как в исходных текстах, так и в исполняемой форме для разных платформ, доступен
с сайта www.ruby-lang.org.
Там же можно найти и документацию (к сожалению, только на английском языке). Сайт
www.rubycentral.com
открывает доступ к представленной в html- и xml-форматах книге "Программируя
на Ruby".


Сообщить об опечатке

Текст, который будет отправлен нашим редакторам: