Domain Specific Language с помощью Ruby


Содержание

Репозитории Ruby DSL (Domain Specific Language), примеры

Я ищу отличные примеры Ruby DSL (Domain Specific Languages). Какие репозитории, проекты, которые вы знаете, стоит прочитать? Почему это (или: они) отличные примеры?

Мне особенно интересны более сложные примеры, которые хорошо продуманны и разработаны.

Rake and Rack — неплохие примеры DSL. Если вам нужны еще несколько примеров, проверьте их:

  • Sinatra — очень популярный DSL для создания веб-приложений, и он открывает исходный код на GitHub.
  • Twibot — это новый DSL, вдохновленный Sinatra, который позволяет создавать ботов Twitter, которые автоматически отвечают на сообщения и ответы.

Если вы хотите начать свой собственный, вот отличный учебник под названием Построение DSL в Ruby.

В области Behavior-Driven Development вы можете проверить:

  • Cucumber — Опишите BDD, используя сценарии
  • RSpec — Замените тестовый код указанием поведения.

Хотя я должен признать, что код RSpec оставляет меня иногда царапать мою голову, потому что я все еще очень начинающий.

Другим примером, конечно, является Rake, система сборки Ruby. Что делает DSL «хорошим» на мой взгляд:

  • Обозначение соответствует значению, т.е. если вы читаете предложение (утверждение) в DSL, у вас есть четкое, однозначное представление о том, что он делает.
  • Домен, т.е. DSL не решает каждую проблему во Вселенной, а фокусируется на одном небольшом домене (таком как создание программного обеспечения, запрос данных или создание пользовательских интерфейсов).
  • Высокий уровень абстракции. DSL использует концепции высокого уровня, которые может использовать программист, и переводит их на более низкую реализацию (внутренне). В случае с Rake основной концепцией, на которой основан язык, являются задачи и зависимости между ними.

Вы проверили Docile gem, это может быть самый простой и самый чистый способ удовлетворить ваши потребности?

Технология Клиент-Сервер 2005’3
Home Поиск Издания Контакты k-press.ru

Автор: Мартин Фаулер
Перевод: А.Максимова

Опубликовано: 26.02.2006

Новые идеи в области разработки программного обеспечения, как правило, представляют собой лишь вариации на тему старых. В этой статье я расскажу об одной из таких идей, а именно о создании приложений, которые я называю «языковым инструментарием»: Intentional Software, Meta Programming System, которую разрабатывает компания JetBrains и Software Factories, которые делают в Microsoft. Все эти приложения используют давно известный подход к разработке программного обеспечения — назовем его «языкоориентированным программированием». Благодаря использованию в них инструментария современных сред разработки (IDE), этот вид программирования становится гораздо более жизнеспособным. Как бы там ни сложилось в будущем, я уверен, что этот вид приложений на настоящий момент является самым интересным явлением на горизонте нашей индустрии. И уж достаточно интересным для того, чтобы написать это эссе и попробовать изложить хотя бы в общих чертах их суть и возможную выгоду от их использования.

Давным-давно существует такой стиль программирования, при котором программные приложения пытаются описывать с помощью языков, специфичных для соответствующей предметной области. В Unix это «малые языки», которые генерируют программный код посредством lex и yacc; в Lisp такие языки разрабатывают внутри самого Lisp, чаще всего используя его макросы. Однако такой стиль программирования пока не нашел широкого применения и популярен только среди его ярых сторонников, к немалому их огорчению.

Последние несколько лет можно было наблюдать за попытками создать новый вид программных систем, которые как раз и призваны поддерживать «языкоориентированный» стиль программирования. Самая ранняя (и наиболее известная) из них – это Intentional Programming, концепция которого была разработана Чарльзом Симони (Charles Simonyi) еще в Microsoft. Но в этой области работают и другие исследователи, и вот как раз их деятельность вызвала новую волну интереса к данной области.

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

В этой статье всего два новых базовых термина: «языкоориентированное программирование» (Language Oriented Programming) и «языковой инструментарий» (Language Workbench). Языкоориентированное программирование – это общее название для стиля разработки программного обеспечения, который строится вокруг использования ряда языков, специфичных для данной предметной области. Языковой инструментарий – это общий термин для всех видов приложений, разработанных для поддержки языкоориентированного программирования. Иными словами, языковой инструментарий дает возможность заниматься каким-то одним видом языкоориентированного программирования. Возможно, вам незнаком термин » язык предметной области» (Domain Specific Language), чаще передаваемый аббревиатурой DSL. Это урезанная форма языка программирования, созданная специально для решения определенного вида проблем. В некоторых сообществах программистов под DSL принято понимать только языки, создаваемые для решения конкретной задачи, но я предпочитаю использовать термин DSL для описания языков, которые могут использоваться для решения некоторого ограниченного класса проблем.

Хотелось бы кратко описать ситуацию, сложившуюся в настоящий момент в сфере языкоориентированного программирования, показать на примере, что это такое, а также рассказать о всевозможных течениях, которые существуют в этой области, а также спорах и аргументах «за» и «против». Конечно, если вы уже наслышаны о языкоориентированном программировании, вам захочется пропустить эту часть. Однако я уже убедился, что много (можно даже сказать, абсолютное большинство) программистов не так уж хорошо в этом ориентируются. Давайте же разберемся сначала с общей концепцией, а потом перейдем к тому, что такое языковой инструментарий, и как существующие его разновидности могут изменить отношение к языкоориентированному программированию в целом.

Пока я писал, выяснилось, что для одной статьи материала слишком много. Пришлось выделить некоторые части в отдельные небольшие статейки. Далее в тексте я буду указывать, когда стоит прерваться и переключиться на дополнительную статью. Ссылки на них вы найдете в тексте статьи. Обратите особое внимание на «Пример использования MPS» (http://martinfowler.com//articles/mpsAgree.html) – там я показываю, как создать DSL с помощью одного из существующих языковых инструментариев. Мне кажется, это лучший способ почувствовать, что будут представлять собой подобные приложения в будущем. Впрочем, сначала вам нужно обратиться к общему описанию, иначе от конкретных примеров будет мало толку.

Простейший пример языкоориентированного программирования

Начну с самого простого примера языкоориентированного программирования и типичной ситуации, когда оно может понадобиться. Представьте, что нужна система, которая считывает файлы и создает на их основе некие объекты. В каждой строке файла хранятся данные для создания одного объекта. Эти объекты могут принадлежать разным классам, соответствующий класс обозначается четырехзначным кодом в начале строки. Далее в строке идут данные для полей класса, причем поля у разных классов могут быть разными. Поля определяются скорее своей позицией, нежели разделителями. Так, идентификатор клиента может располагаться между 4-ым и 8-ым символом записи.

Вот пример таких данных:

Точки здесь обозначают какие-то ненужные и неинтересные данные. Закомментированная строка сверху показывает расположение символов. Первые четыре символа обозначают тип данных – SVCL – Service Call, USGE — Usage. Символы с 5-ого по 18 после SVCL указывают имя клиента.

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

У меня для этого существует очень простой класс. Он может быть параметризован коллекцией классов-стратегий для считывания объектов определенного типа. Для данного примера понадобится всего две стратегии – одна для Service Call, другая – для Usage. Стратегии я сохраняю в ассоциативном массиве (map), ключом для которого служит строковый код.

Вот код для обработки файла:

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

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

Экстракторы полей можно добавить в стратегию уже после ее создания.

При обработке строки стратегия создает целевой класс и использует список экстракторов для заполнения его полей.

Экстракторы, в свою очередь, берут значения из соответствующей позиции в строке и проставляют их в поле созданного целевого объекта с помощью механизма reflection.

Пока что для решения нашей задачи я описал очень простую библиотечку. В сущности, я построил некую абстракцию, которую могу использовать для решения вполне конкретных задач. Для использования этой абстракции я должен создать ряд стратегий и указать их в Reader. Вот два примера того, как это может быть сделано.

Посмотрим на эти два различных стиля кодирования. Классы Reader и ReaderStrategy описывают абстракции, в то время как последний фрагмент кода – чистой воды конфигурация. При создании подобных библиотек очень часто бывает удобно думать в этих двух плоскостях – абстракция и конфигурация. Абстракция может представлять собой библиотеку классов, фреймворк для разработки, или просто набор вызовов функций. Абстракцию можно легко использовать в нескольких проектах, хотя часто такой необходимости и не возникает. С другой стороны, код конфигурации специфичен для проекта, довольно прост и прямолинеен.

Так как конфигурация довольно проста и, скорее всего, будет меняться чаще, чем абстракция, ее отделяют еще дальше. В нашем примере ее можно вообще вычленить из C#-кода и перенести в XML-файл (сейчас это модно).

XML – очень полезная вещь, но его не очень легко читать. Легче было бы показать, что имется в виду, с помощью собственного синтаксиса. Например, вот так:

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

Как видите, в результате получился очень маленький язык программирования, пригодный (исключительно) для отображения полей фиксированной длины на классы. Это классический пример традиционных ‘малых языков’ UNIX (http://martinfowler.com//articles/languageWorkbench.html#unixLittleLanguage). Для данной задачи это и будет язык предметной области, то есть DSL.

Этот язык хорошо демонстрирует сразу несколько характерных для DSL-языков черт. Во-первых, его можно использовать лишь для очень ограниченного числа задач, ведь он умеет только отображать некие записи фиксированной длины на классы. В результате этот DSL получился очень простым – в нем нет никаких управляющих структур или чего-то подобного. Он даже не является полным по Тьюрингу. Вы не сможете написать целое приложение на этом языке – все, что он позволяет, это описать малюсенький аспект целого приложения. Соответственно, чтобы сделать хоть какую-то законченную работу, этот DSL должен быть объединен с другими языками. И тем не менее, простота языка означает простоту его редактирования и транслирования (ниже я более подробно рассмотрю различные «за» и «против» DSL)

Посмотрим теперь на XML-вариант. Является ли он DSL? Я бы сказал, что да. Он использует XML в качестве синтаксиса, но по-прежнему остается DSL и во многом разделяет свойства языка из предыдущего примера.

Сейчас самый подходящий момент дать понятие разграничения, с которым всегда сталкиваешься, когда говоришь о языках программирования: а именно различие между абстрактным и конкретным синтаксисами. Конкретный синтаксис языка – это синтаксис, который мы видим, когда смотрим на код. Конфигурации, выраженные через XML и через наш собственный формат файлов, имеют различные конкретные синтаксисы. Однако и тот, и другой используют сходную структуру – существует ряд отображений, для каждого из которых заданы строковый код, имя целевого класса и набор полей. Эта базовая структура является абстрактным синтаксисом . Когда разработчики размышляют над синтаксисом языка программирования, они, в большинстве случаев, не делают такого разграничения. И тем не менее, оно играет большую роль при использовании DSL. Об этом можно думать двояко: либо у вас есть один язык с двумя конкретными синтаксисами, либо два языка с одним и тем же абстрактным синтаксисом.

Таким образом, этот пример приводит к интересному вопросу проектирования: что лучше — собственный конкретный синтаксис для DSL или конкретный синтаксис XML? Возможно, синтаксис XML легче читать программно, поскольку для XML существует множество библиотек и инструментов. С другой стороны, приведенный выше синтаксис проще. Готов поспорить, что его гораздо легче прочитать глазами (по крайней мере, в данном случае). Но как бы там ни было, наш случай не может изменить общее соотношение «за» и «против» DSL. Да и вообще, вы всегда можете смело утверждать, что любой конфигурационный XML-файл на самом деле является DSL.

Давайте сделаем шаг назад и обратимся к конфигурационному коду на C#. DSL это или нет?

Этот кусок кода похож на код на C#. Те, кто знает о моих предпочтениях в языках программирования, могут предположить, что последний пример – это код на Ruby. На самом же деле это точный «моральный эквивалент» примера на C#. Он куда больше похож на DSL благодаря различным особенностям Ruby: ненавязчивому синтаксису, использованию специальных конструкций для выражения диапазонов литералов и гибкостью в runtime-вычислениях. Это полный конфигурационный файл, который может быть считан и обработан в области видимости экземпляра объекта в runtime-е. Но это по-прежнему чистый Ruby-код, который взаимодействует с кодом фреймворка через вызовы методов mapping и extract, соответствующих AddStrategy и AddFieldExtractor в примере на C#.

Я бы сказал, что оба примера – и на C#, и на Ruby – представляют собой DSL. В обоих случаях используется подмножество возможностей основного языка и реализуются идеи, которые выражаются через XML или специальный синтаксис. По сути, DSL внедряется в основной язык, используя его подмножество как особый синтаксис для абстрактного языка. В общем-то, это скорее вопрос отношения к проблеме, чем что-либо другое. Я же решил смотреть на код C# и Ruby через призму языкоориентированного программирования. Кстати, у этой точки зрения глубокие корни – именно так Lisp-программисты создают DSL внутри этого языка. Конечно, то, что волнует авторов таких внутренних DSL, будет отличаться от того, что волнует программистов, пишущих внешние DSL. И тем не менее, общего между ними довольно много (позже я рассмотрю этот вопрос подробнее).

Теперь, когда я привел пример DSL, мне будет проще дать определение языкоориентированному программированию. Языкоориентированное программирование описывает разрабатываемые системы при помощи всевозможных DSL. В системе, где лишь малая часть функциональности написана на DSL, языкоориентированного программирования мало. А там где на DSL написана большая часть функциональности, языкоориентированного программирования много. Сложно понять, много или мало языкоориентированного программирования у вас в системе, особенно если вы используете внутренние DSL. И здесь, как и в случае с любым другим повторно используемым кодом, вы можете написать DSL сами, или же взять какие-нибудь чужие.

Традиции языкоориентированного программирования

Как видите, языкоориентированное программирование – вещь далеко не новая, и программисты уже давным-давно используют его в работе. Давайте же обратимся к текущему положению дел в этой отрасли, а потом уже посмотрим, какие изменения в картину внесут языковые инструментарии.

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

«Малые языки» Unix

«Малые языки» UNIX являются наиболее очевидными представителями мира DSL. Эти языки представляют собой внешние DSL-системы, которые, как правило, используют для трансляции встроенные инструменты Unix. В бытность мою студентом, я нередко забавлялся с lex и yacc (инструменты такого рода являются стандартными программами Unix). С их помощью гораздо легче писать парсеры и генерировать код (зачастую на C) для малых языков. Awk – еще один пример такого мини-языка.

Lisp представляет собой, пожалуй, самый яркий пример существования различных DSL прямо в самом языке. Символьная обработка включена и в название, и в повседневную работу «лисперов». Этому способствуют минималистический синтаксис Lisp, его замыкания (closures) и макросы (программисты часто при упоминании макросов представляют себе макросы в стиле С/C++; макросы в Lisp не имеют к ним практически никакого отношения – они функционируют на синтаксическом уровне и являются средством метапрограммирования, в С/C++ их можно сравнить с метапрограммированием на шаблонах, но макросы Lisp более мощные и гибкие – прим. ред.). Все это является отличной закваской для создания DSL. Пол Грэхем много пишет об этом стиле программирования (http://www.paulgraham.com/progbot.html). Похожие традиции разработки давно существуют и в языке Smalltalk.

Активные модели данных

Программисты, создающие сложные модели данных, могут продемонстрировать, как регулярно меняющиеся части системы могут быть закодированы с помощью информации в таблицах базы данных (их еще называют «таблицами с метаданными», а программы – «программами, управляемые таблицами»). В самой программе эти данные определенным образом интерпретируются и определеют поведение системы.


На самом деле в этом случае мы имеем дело с DSL, конкретный синтаксис которого содержится в таблицах базы данных. Довольно часто содержимое этих таблиц редактируется через GUI. Как правило, люди, которые с этим работают, даже не задумываются о создании какого-либо языка, а сложность работы с реляционным конкретным синтаксисом не позволяет таким языкам чрезмерно разрастаться и терять первичную направленность.

Адаптивные объектные модели

Поговорите с матерыми ООП-программистами, и они поведают вам о созданных ими системах, которые построены на основе компоновки различных объектов в гибком и мощном программном окружении. Такие системы строятся для сложных моделей предметной области, когда большая часть поведения системы определяется набором объектов, объединенных общей конфигурацией. Такой механизм позволяет строить различные сложные системы сходным образом. Сторонники OO-подхода используют адаптивные объектные модели (www.adaptiveobjectmodel.com) как адаптивные модели данных «на стероидах».

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

Конфигурационные файлы XML

Никто не возразит, когда вы заметите, что в любом современном Java-проекте меньше Java, чем XML. Крупные системы задействуют сразу несколько программных каркасов, большинство из которых используют для конфигурации XML-файлы. По сути, эти файлы представляют собой DSL. XML-файлы легко читаются программно, хотя человеку может оказаться куда проще прочитать файлы в специально созданном формате. Для тех, кому угловые скобки режут глаз, разработчики даже пишут специальные IDE-плагины для работы с XML-файлами.

Инструменты для построения пользовательских интерфейсов (GUI builders)

Вместе с появление графических пользовательских интерфейсов появились инструменты, позволяющие создавать такие интерфейсы путем перетаскивания элементов управления. Visual Basic, наверное, самый известный из таких инструментов, хотя я пользовался похожими программами для символьных терминалов задолго до того, как в моду вошли графические интерфейсы. Такие программы либо сохраняют информацию о расположении элементов управления в собственном формате и генерируют пользовательский интерфейс во время выполнения программы на основе этой информации, либо пытаются заложить всю требуемую информацию в сгенерированный код. Хотя внешне такой стиль работы с графическим интерфейсом выглядит весьма заманчиво (особенно, если посмотреть рекламные демо-ролики), у него есть определенные ограничения. Причем ограничения эти настолько существенны, что многие опытные разработчики интерфейсов предпочитают вообще не пользоваться такими инструментами для создания мало-мальски сложных приложений.

Инструменты для построения пользовательских интерфейсов представляют собой разновидность DSL, однако здесь редактирование кода существенно отличается от редактирования кода в текстовых программных языках, к которым мы так привыкли. Даже сами их разработчики часто не рассматривают созданные им приложения для построения интерфейсов как языки программирования. Некоторые считают, что в этом и состоит основная проблема всех подобных инструментов.

Языкоориентированное программирование: за и против

Итак, мы видим, что существующие виды и формы языкоориентированного программирования довольно популярны. Мне кажется, что описывая этот вид программирования в целом, будет удобно разделить все его разновидности на две основные категории: внешние и внутренние DSL. Внешние DSL пишут не на том языке, на котором написано все приложение, а на каком-то другом, после чего такой DSL трансформируется в приложение с помощью компилятора или интерпретатора. К таким DSL относятся «малые языки» Unix, активные модели данных, конфигурационные XML-файлы. Что касается внутренних DSL, то они превращают в DSL сам основной язык приложения. Лучшим примером такого языка служит Lisp.

Термины «внешний DSL» и «внутренний DSL» были придуманы специально для этой статьи. Дело в том, что мне так и не удалось найти в существующей литературе подходящую пару терминов, которые бы хорошо описывали эту разницу в DSL. Внутренние DSL иногда называют «встроенными» (embedded), но я бы не рекомендовал использовать этот термин, потому что его можно отнести к языкам, встроенным в приложение (например, встроенный в Word язык VBA, который можно посчитать разве что внешним DSL). Однако если вы обратитесь к другим источникам, то наверняка встретите термин «встроенный DSL».

Плюсы и минусы внутренних и внешних DSL настолько отличаются друг от друга, что лучше рассмотреть каждый вид этих языков отдельно.

Внешние DSL

Внешними я называю те DSL, которые написаны на языке, отличном от основного языка программного приложения. Примерами DSL такого типа могут служить «малые языки» Unix и конфигурационные XML файлы.

Самый большой плюс внешних DSL состоит в том, что их можно писать так, как вам вздумается. Иначе говоря, вы можете выразить предметную область в самой простой и доступной для чтения и редактирования форме. Формат такого DSL будет ограничен лишь вашим умением создать транслятор, который сможет зачитать конфигурационный файл и выдать некий выполняемый код – как правило, уже на основном языке приложения.

Отсюда же следует и очевидный недостаток внешних DSL – вам придется писать этот самый транслятор. Если язык несложен, как в том примере, что я привел выше, это будет совсем нетрудно. Более сложный язык потребует больших усилий, но с этим тоже можно справиться. Существуют ведь и генераторы лексических анализаторов, и другие инструменты для создания компиляторов, которые облегчают работу со сложными языками. К тому же, в самих DSL заложена идея простоты и читабельности. Так, XML ограничивает форму DSL, но при этом делает его очень удобным для чтения.

Еще один существенный недостаток внешних DSL состоит в том, что у них нет того, что я называю «символической интеграцией». То есть, на самом деле, DSL никак не связан с основным языком приложения. Программная среда разработки базового языка, на котором пишется приложение, ничего не знает о нашем новом DSL. Сейчас, когда программные среды разработки становятся все более изощренными, эта проблема стоит довольно остро.

К примеру, захотелось нам переименовать свойства целевого класса в том примере, который я уже приводил выше. Во всех современных средах разработки автоматическим переименованием уже никого не удивишь. Однако это переименование не будет работать в коде DSL. Это и есть тот самый «символический барьер» между миром C# и DSL для отображения файлов. Такое отображение можно транслировать в С#, но этот барьер не позволяет манипулировать программой как единым целым.

Недостаток интеграции будет сказываться постоянно. Во-первых, где писать код на DSL? Конечно, можно обойтись и простым текстовым редактором, но после современных сред разработки такие редакторы выглядят слишком уж примитивно. Мне же нужен всплывающий список всех имен полей, да еще чтобы работало автодополнение слов (completion). Я хочу, чтобы перекрывающиеся интервалы для различных полей были подчеркнуты красной волнистой линией, обозначая ошибку. Однако для этого мне понадобится такой редактор, который понимал бы семантику моего DSL.

Ну хорошо, я могу прожить и без редактора, который понимает семантику языка. А как быть с отладкой? Мой отладчик сможет работать с C#-кодом, полученным для моего DSL, но он не сможет работать с исходным кодом самого DSL. Было бы здорово, чтобы иметь собственную полнофункциональную среду разработки для моего DSL! Ведь те дни, когда код писали в простых текстовых редакторах и пользовались простыми отладчиками, уже канули в Лету. Теперь мы живем в эру post-IntelliJ (см. врезку), и все изменилось.

Чаще всего против использования внешних DSL выдвигают обвинение в «языковой какофонии». Суть его в следующем: программные языки довольно сложно освоить, поэтому задействовать сразу несколько языков в одной программе – значит, чрезмерно усложнить ее. На самом деле это просто небольшое недоразумение, которое проистекает из непонимания сути DSL. Те, кто говорит о «языковой какофонии», представляют себе мешанину из программных языков общего назначения – картину, действительно, жуткую. Однако DSL куда ограниченнее и проще, чем языки общего назначения. К тому же они тесно связаны с предметной областью, и изучать их гораздо легче.

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

Многие говорят о том, что создавать DSL сложно. Проектирование новых программных языков – вещь непростая, а писать несколько языков для каждого проекта будет просто непосильной тяжестью. Опять-таки, это обвинение коренится в том же недопонимании сути DSL, в том что они коренным образом отличаются от программных языков общего назначения. Самая сложная часть задачи, как мне кажется — построить DSL на хорошей абстракции. Это самое главное. А дальше я не вижу большой разницы между проектированием DSL и проектированием API. Не думаю, что создавать DSL намного сложнее, чем спроектировать хороший API.

Многие считают, что самый большой плюс внешних DSL состоит в том, что они вычисляются во время выполнения. Это дает возможность поменять часто изменяющиеся параметры без повторной компиляции программы. Именно поэтому конфигурационные XML-файлы так популярны в мире Java. Впрочем не стоит забывать, что это верно только для статически компилируемых языков. Существует целый ряд языков, код которых может быть проинтерпретирован во время выполнения, так что для них это не плюс. Кроме того, растет интерес к смешению компилируемых и интерпретируемых языков, например, IronPython в .NET. Это позволяет выполнить внутренний DSL IronPython прямо в контексте системы, написанной на C#. Те, кто работают с Unix, часто смешивают С и С++ с различными языками сценариев.

Внутренние DSL

Плюсы и минусы внутренних DSL являются зеркальным отражением внешних. Здесь уже нет символьного барьера. Можно в полную силу пользоваться возможностями основного языка приложения и при этом задействовать все инструменты, которые есть для этого языка. В качестве примера внутренних DSL можно назвать Lisp и адаптивные объектные модели.

Но здесь мы сталкиваемся с довольно серьезной проблемой, которая состоит в существенных различиях между основными «языками с фигурными скобками» (C, C++, Java, C#) и языками типа Lisp, которые действительно хорошо подходят для создания внутренних DSL. Стиль внутренних DSL гораздо более приемлем в Lisp или Smalltalk, чем в Java или C# — и действительно, сторонники динамических языков часто указывают на это как на одно из основных их преимуществ. Сейчас мы наблюдаем, как это происходит с некоторыми сценарными языками — в качестве примера можно привести мета-программирование в Ruby (http://poignantguide.net/ruby/chapter-6.html) и его использование в Ruby On Rails (http://www.rubyonrails.org/).

Внутренние DSL ограничены синтаксисом и структурой основного языка приложения. Чем более динамичен язык, тем меньше он страдает от этого ограничения. Такие языки обладают минималистским синтаксисом (Lisp, Smalltalk, языки сценариев), и это делает их хорошей основой для DSL, в отличие от «языков с фигурными скобками». Вы уже имели возможность в этом убедиться, сравнив примеры на C# и Ruby. Большую роль при создании внутренних DSL играют замыкания (closures) и макросы. В C-подобных языках большая часть подобных механизмов отсутствует, но у них есть другие возможности для создания внутренних DSL. Одна из них – аннотации (атрибуты в C#).

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

Возможность предоставить DSL всю мощь основного языка программирования – палка о двух концах. Если вы свободно владеете базовым языком, все хорошо. Однако сильная сторона DSL как раз в том и состоит, чтобы программировать, не зная всех возможностей базового языка. Это и позволяет непрофессиональным программистам вводить данные предметной области непосредственно в систему. Внутренний DSL может усложнить этот процесс, ведь существует масса мест, где пользователь может зайти в тупик из-за недостаточной осведомленности о возможностях базового языка.

Можно посмотреть на это и с такой точки зрения: языки общего назначения предлагают множество языковых конструкций и возможностей. Ваш DSL использует только часть этих возможностей. Когда инструментов больше, чем нужно, это только замедляет работу – ведь придется разбираться в них, чтобы выбрать нужные. В идеале хочется иметь только то, что нужно для выполнения работы, ну разве чуточку больше. (Чарльз Симони обсуждает эту идею, используя нотацию степеней свободы, http://blog.intentionalsoftware.com/intentional_software/2005/05/notations_and_p.html)).

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

Внутренние DSL очень близки к языкам программирования, поэтому с их помощью будет сложно выразить нечто, для чего в языке программирования нет соответствующего механизма. Например, в корпоративных приложениях часто возникает понятие слоя (layer). По большей части слои можно определить с помощью пакетов в языке программирования. Однако через пакеты нельзя задать зависимости между слоями. То есть весь интерфейсный код можно разместить в пакете MyApp.Presentation, а бизнес-логику в MyApp.Domain, но вы не сможете через внутренний DSL задать такое ограничение, чтобы классы из MyApp.Domain не ссылались на классы из MyApp.Presentation. До какой-то степени это еще раз иллюстрирует ограниченность динамизма в общеупотребительных языках программирования – ибо подобные вещи были возможны в Smalltalk, где программист имел больший доступ к метаинформации.

(Для сравнения можно взглянуть на более сложный пример, который я писал на одном из таких динамических языков (http://martinfowler.com//articles/mpsAgree.html). Вряд ли я буду возвращаться к этому примеру, но если кто-то это сделает, то я обновлю раздел «Дополнительное чтение».)

И для не-программистов

Одна из тем, постоянно всплывающих в рассуждениях о языкоориентированном программировании — возможность создания кода непрофессионалами. Всевозможные эксперты в различных предметных областях могли бы программировать, используя соответствующие DSL. Впрочем, подобная цель стоит перед программированием уже давно. Многие были когда-то уверены, что ранние языки высокого уровня (Cobol, Fortran) положат конец профессии программиста, потому что пользователи смогут программировать на них сами. А мне кажется, что это просто «синдром Cobol», и все те технологии, которые якобы должны свести на нет профессиональное программирование, ни к чему подобному не приведут.

Независимо от «синдрома COBOL-а», порой действительно удается сделать так, чтобы пользователи вносили существенный вклад в написание программ. Вот один из способов: выделить некую часть проблемы (не очень сложную и имеющую четкие границы) и позволить пользователям программировать именно в этой части. Впоследствии такие области можно превратить в DSL. Кстати, подобные DSL могут быть весьма сложными – возьмите, к примеру, MatLab. Это довольно сложный DSL, выживающий именно потому, что он жестко сфокусирован на своей предметной области.

Преимущество внешнего DSL для использования непрофессионалами состоит в том, что можно смело отказаться от всех сложностей базового языка приложения, и создать DSL, который будет понятен и удобен пользователям. Это особенно важно для языков с ограничивающим синтаксисом. Однако даже если вы имеете дело с простыми языками, внутренние DSL будут менее удобны: пользователь может сделать то, что кажется логичным с точки зрения языка, но будет выходить за рамки DSL. В результате он получит странное поведение программы или таинственные сообщения об ошибках, что легко вгонит его в ступор.

Многие адепты языкоориентированного программирования описывают, как в скором будущем вся предметная логика системы будет создаваться пользователями, а программисты будут лишь писать для них необходимые инструменты поддержки, позволяющие компилировать такие программы. Конечно, это не означает конца профессии «программист», однако едва ли при таком раскладе их понадобится много (ведь эти самые инструменты поддержки можно будет использовать повторно). Кроме того, при таком сценарии исчезнут все проблемы взаимодействия между программистами и заказчиками, которые так осложняют процесс программирования в наши дни. Красивая идея, ничего не скажешь. И все же это очень напоминает «синдром Cobol».

Конечно, подключение непрофессионалов к программированию – весьма ценная вещь, но я не думаю, что это самый веский аргумент в пользу языкоориентированного программирования. Хороший DSL позволяет работать более продуктивно и профессиональному программисту, даже если этим DSL не смогут пользоваться непрофессионалы. Возможно, хороший DSL должен изначально писать профессиональный программист, а уже потом его будут уточнять и пересматривать эксперты в предметной области.

Использование труда непрофессиональных программистов – рискованная ставка, но и выигрыш тут большой. Я ни за что не поверю в разговоры о том, что новую технологию подхватит огромное число пользователей и тут же начнет писать программы не хуже профессиональных программистов. Но если бы это все же удалось, то выгоду от внедрения такой технологии трудно было бы переоценить. И дело вовсе не в том, что профессиональных программистов станет меньше (или не будет вовсе). Дело в том, что можно было бы исправить то ужасное состояние дел, которое наблюдается сейчас в области взаимодействия программистов и экспертов в предметных областях. А это, по моему мнению, является камнем преткновения для успешной работы над проектами.

Подведем итоги

С моей точки зрения, самое большое достоинство языкоориентированного программирования – это возможность использования DSL, а недостаток – необходимость создавать средства для их поддержки. Если писать внутренние DSL, то уменьшится необходимость в средствах поддержки. Однако вместе с этим уменьшатся и возможности самого DSL – на него будут наложены ограничения, связанные с базовым языком (особенно если вы пишете на C-подобных языках). Внешние DSL не ограничивают ваши возможности, однако требуют усилий по проектированию самого языка, созданию транслятора, а также некоего инструментария, который будет помогать вам программировать.

Именно поэтому языкоориентированное программирование так медленно распространяется: и внешние, и внутренние DSL имеют весьма ощутимые недостатки. Вот в результате мы и мучаемся ощущением того, что с помощью DSL можно работать куда эффективнее, только непонятно, как.

И это естественным образом подводит нас к теме языкового инструментария. Итак, по сути своей, эти инструменты должны предоставить программистам широту возможностей внешних DSL и при этом уничтожить «семантический барьер». Более того, они должны позволять с легкостью создавать вспомогательные инструменты, не уступающие современным средам разработки. В результате языкоориентированное программирование станет доступным для тех, кого до сих пор отпугивали связанные с ним неудобства.

Составляющие языкового инструментария

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

Одной из сильных сторон языковых инструментариев является то, как они изменяют соотношение между редактированием и компиляцией программы. По сути, они дают возможность редактировать не текст программы, а некое абстрактное ее представление. Давайте остановимся на этом чуть-чуть подробнее.

В обычном программировании мы редактируем текст программы с помощью текстового редактора. После этого мы при помощи транслятора делаем исходный код исполняемым, то есть превращаем текст в то, что компьютер сможет понять и исполнить. Трансляция может происходить во время выполнения, как, например, в скриптовых языках (Python, Ruby), или же представлять собой отдельный шаг, как в транслируемых языках типа Java, C# и С.

Рисунок 1: Традиционная схема компиляции программы.

Разобьем этот процесс на составляющие. На рисунке 1 изображен упрощенный процесс компиляции программы. Чтобы файл foo.cs стал исполняемым, надо запустить компилятор. Учитывая контекст нашей беседы, давайте разобьем весь процесс компиляции на два шага. Во время первого на основе текста из файла foo.cs компилятор строит абстрактное синтаксическое дерево (АСТ). Во время второго шага происходит обход этого дерева и формируется CLR байт-код, который затем помещается в скомпонованный файл (exe).

Более подробно о генерации кода из внешнего DSL читайте в статье Generating Code for DSLs (http://martinfowler.com//articles/codeGenDsl.html).

Цукерберг рекомендует:  Ускоренная верстка

Можно сказать, что существует несколько разных представлений программного приложения, а компилятор осуществляет трансляцию между ними. Файл с исходным кодом представляет собой редактируемое представление программы – именно с ним мы работаем, когда хотим изменить что-то в программе. Кроме того, это также и вид хранения программы, потому что при необходимости мы будем обращаться к исходному коду. Когда мы запускаем компилятор, на первом этапе он создает отображение редактируемого файла в виде абстрактного представления программы (абстрактное синтаксическое дерево), а потом генератор кода превращает его в еще одно представление – исполняемое (CLR байт-код).

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

Абстрактное представление очень недолговечно – оно существует только во время работы компилятора. Используется оно с одной лишь целью – разделить процесс компиляции на два последовательных шага. Разумеется, эта недолговечность является одной из самых серьезных причин, по которым мы не можем добиться символической интеграции между внешними DSL. Каждый из таких языков будет проходить через собственный процесс компиляции, поэтому их абстрактные представления окажутся никак не связанными между собой. Все соединтся лишь на последней стадии – в сгенерированном коде, когда все ключевые абстракции уже утеряны.

Сложные современные среды разработки (IDE), которые появились после IntelliJ IDEA, существенным образом изменили эту модель. Когда среда разработки загружает файл, в ее памяти создается его абстрактное представление, которое помогает редактировать файл. (В языке Smalltalk было нечто похожее.) С помощью абстрактного представления файла мы можем делать такие простые вещи, как автодополнение имени метода, или же такие сложные как рефакторинг (автоматизированный рефакторинг представляет собой трансформацию абстрактной модели).

Один из моих коллег, Мэтт Фёммель, рассказывал, как однажды во время работы в IntelliJ IDEA он с удивлением понял, что, занимаясь рефакторингом, он практически не пишет код, а лишь манипулирует абстрактным представлением программы. Разумеется, после этого IDE отображала эти изменения в тексте программы, но суть от этого не менялась – Мэтт менял не код, а абстрактное представление. Если вам тоже доводилось ощущать нечто подобное во время работы с современной средой разработки, то вы уже представляете, что такое работа с языковым инструментарием.

Рисунок. 2: Манипулирование различными представлениями программы с помощью языкового инструментария.


На рисунке 2 изображен тот же процесс, но уже в языковом инструментарии. Самое главное отличие – «исходным» становится уже не редактируемый текстовый файл. Здесь вы изменяете само абстрактное представление программы. Чтобы у вас была возможность редактировать абстрактное представление, оно должно подаваться в каком-нибудь редактируемом виде. И теперь уже редактируемое представление программы будет недолговечным (оно нужно только чтобы облегчить работу). А настоящим «исходником» программы будет постоянно существующее абстрактное ее представление.

Тот факт, что редактируемое представление программы является всего лишь временной проекцией, подводит к нескольким интересным выводам. И возможно, самым важным из них будет то, что редактируемое представление совсем не обязательно должно быть полным и законченным. Если отдельные его составляющие не важны для текущей задачи, то их вообще можно не показывать. Более того, редактируемых представлений может быть несколько, и каждое из них призвано иллюстрировать некоторые аспекты абстрактного представления. А поскольку абстрактная проекция находится внутри языкового инструментария, то редактируемое представление будет гораздо активнее, чем обычный текстовый файл. Таким образом, редактор абстрактной проекции окажется тесно связан с языком программирования, а следовательно во время работы с редактируемыми представлениями вы будете активно думать о том, как преобразовывает их редактор. Возможно, у вас будут возникать гораздо более интересные идеи, чем те, которые вы можете придумать при работе с таким пассивным видом редактируемого материала, как текст.

Языковой инструментарий отделяет представление, в котором хранится код, от его редактируемой ипостаси. Теперь хранение кода осуществляется через сериализацию абстрактной проекции программы. Обычно это делается с помощью XML, однако в данном случае этот XML уже не предназначен для редактирования человеком. Выбор XML для хранения программного кода облегчает взаимодействие приложения с другими системами. Хотя, конечно, достичь такого взаимодействия будет очень и очень непросто.

С генерацией кода происходит похожая ситуация, хотя обычно кодогенераторы рассматривают в качестве исполнимого кода обычные исходники. Исходные файлы, созданные обычными кодогенераторами, по сути не являются исходными, потому что они не предназначены для прямого редактирования. По мере развития языкового инструментария мы будем все больше полагаться на такую генерацию не редактируемых структур, например в байт-код.

Абстрактное представление, с которым мы работаем в языковом инструментарии, должно быть терпимо к ошибкам и всевозможным неточностям. Возможно, это не самая очевидная его характеристика, но тем не менее, одна из наиважнейших. Принято считать, что абстрактное представление программы должно быть абсолютно правильным, что в нее нельзя вносить неточную информацию. Так вот, это предположение приводит к отсутствию юзабилити таких приложений. Те среды разработки, которые пошли по стопам IntelliJ IDEA, прекрасно уяснили это, и теперь весьма элегантно обращаются с ошибками в коде. Например, вы сможете заниматься рефакторингом в приложениях, которые компилируются с ошибками (это – признак высокой юзабилити).

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

Определяем новый DSL

Наметив общие цели и установки языкового инструментария, обратимся теперь к процессу создания новых DSL. Он состоит из трех шагов:

  • Определить абстрактный синтаксис, который станет схемой абстрактного представления программы.
  • Выбрать редактор , который позволил бы людям манипулировать абстрактным представлением с помощью некой проекции.
  • Создать генератор , который будет транслировать абстрактное представление в исполнимое. В действительности генератор определяет семантику DSL.

Разумеется, это только основные пункты, кроме того, в них возможны некоторые вариации. Как я уже говорил ранее, при работе с DSL вполне можно воспользоваться несколькими редакторами и генераторами. У разных людей могут быть разные предпочтения при выборе редактора. Например, в редакторе Intentional можно переключаться между несколькими проекциями одной и той же модели и видеть иерархическую структуру данных как LISP-образные списки, как вложенные блоки или же в виде дерева.

Разные редакторы понадобятся по нескольким причинам. Какие-то из них могут оказаться удобнее для работы с определенными фреймворками, поддерживающими схожую функциональность. Хороший пример такого случая – многочисленные диалекты SQL. Кроме того, различные компромиссы в разработке могут возникать из-за различия в характеристиках производительности или библиотеках. И наконец, может понадобиться генерировать код на разных языках – то есть чтобы ваш DSL мог генерировать код и на Java, и на C#.

Кроме того, может оказаться полезной возможность определять трансляторы для хранения данных. Вполне вероятно, что языковой инструментарий будет по умолчанию создавать какую-то схему хранения данных, которая автоматически осуществит сериализацию абстрактной модели. Тем не менее, может понадобиться несколько альтернативных представлений хранения данных. Например, для того, чтобы поддержать возможность взаимодействия между различными системами, или для передачи данных из одного инструмента в другой. В отличие от генераторов кода, в данном случае общение с таким представлением будет двусторонним.

Дополнительный вид генератора может создавать документацию, которую сможет читать человек (эквивалент javadoc). Разумеется, все взаимодействие с языковым инструментарием должно осуществляться с помощью редактора, однако это не отменяет необходимости создавать документацию – как бумажную, так и электронную.

Более подробно об этом написано в статье A Language Workbench in Action – MPS (http://martinfowler.com//articles/mpsAgree.html). В ней показан пример создания новыого DSL с помощью Meta-Programming System (MPS) компании JetBrains. Это реальный пример работающего языкового инструментария.

Создаем новый языковой инструментарий

На сегодняшний день не существует единого определения того, что же представляет собой языковой инструментарий. Неудивительно, что мне пришлось изобретать столько новых терминов для этой статьи! Меня поражает еще и то, что мне приходится определять здесь и основные характерики языкового инструментария. В противном случае, мы столкнемся с такой же жуткой неразберихой, которая окружает большинство тем, касающихся программирования (например, понятия «компонентов» (components), сервисно-ориентированной архитектуры (Service Oriented Architecture) и т.д.).

Впрочем, теперь, после того, как мы довольно подробно рассмотрели многие вопросы, связанные с языкоориентированным программированием, определить базовые свойства языкового инструментария совсем не трудно:

  • Пользователи языкового инструментария могут легко создавать новые языки, которые при этом будут взаимно интегрированы.
  • Главным и основным источником информации является постоянно существующая абстрактная модель приложения.
  • Новый DSL создается в трех основных составляющих: схема, редактор (редакторы) и генератор (генераторы).
  • Пользователи нового языка манипулируют им с помощью проекционного редактора.
  • Языковой инструментарий может работать, даже если абстрактная модель содержит неполную или противоречивую информацию.

Как языковые инструментарии меняют соотношение «за» и «против» в языкоориентированном программировании

Некоторое время назад я говорил о плюсах и минусах языкоориентированного программирования. Языковой инструментарий оказывает непосредственное влияние на соотношение сил в этом вопросе и вносит несколько новых аргументов.

Самый большой плюс языкового инструментария – легкость создания внешних DSL. Теперь не нужно писать парсер. Достаточно определить абстрактный синтаксис – но это обычный этап моделирования данных. К вашим услугам будет мощная среда разработки (IDE). Правда, потребуется некоторое время, чтобы внести нужные установки в ее редактор. Вам все также придется создавать генератор, и мне кажется, что языковый инструментарий не сделает эту задачу проще. Но с другой стороны, написать генератор для хорошего и несложного DSL – задача почти что пустяковая.

Еще одно большое достоинство языкового инструментария состоит в том, что он дает интеграцию на уровне символов. Это же просто здорово – взять похожий на Excel язык формул и просто вставить его в тот язык, который вы сами создали. То же самое можно сказать и о возможности менять символы в одном языке, с тем, чтобы эти изменения прошли через всю систему (языковому инструментарию это вполне по силам, хотя я не уверен, что они уже умеют это на сегодняшний день).

Еще одним важным моментом в использовании языкового инструментария является рефакторинг. Когда я говорю о языковом инструментарии, вы можете представить себе что-то вроде: «Сначала создай DSL, а потом пиши на нем то, что его использует». Если вы знакомы с другими моими работами, то это должно вас насторожить. Да, я большой поклонник эволюционного проектирования, что в данном контексте означает – у вас должна быть возможность одновременно менять и сам язык, и написанный на нем код. Это весьма сложная проблема, и разработчики Intentional с самого начала решили учесть ее. Пока еще нельзя сказать, появится ли в будущих, готовых, версиях языкового инструментария такая возможность, но если нет, то это будет очень большим препятствием к его использованию.

Могу назвать еще одну большую проблему. Это зависимость от поставщика языкового инструментария. Пока не существует никаких стандартов для определения трио «схема, редактор и генератор». Создав язык в каком-либо языковом инструментарии, вы тут же попадаете в зависимость от него. Раз нет никаких стандартных способов обмена данными между разными языковыми инструментария, значит при переходе на другой языковой инструментарий придется создавать заново и схему, и редактор, и генератор. Может быть, с течением времени возникнет некий специальный вид хранения данных – специально для таких случаев. Однако если этого не случится, то риск зависимости от поставщика инструментария будет весьма большим. (Архитектура MDA дает некоторый ответ на эту проблему, но на мой взгляд, он по меньшей мере неполон.)

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

Вопрос выбора инструментария остро встает вследствие ухода от использования текста как носителя исходного кода. Вам придется заново решать различные проблемы, которые уже успешно решались для текстовых файлов. Я имею в виду управление версиями для абстрактных моделей. Мы уже научились делать хорошие инструменты такого рода для текстовых файлов – с возможностью посмотреть разницу в файлах, а затем внести ее в результирующий вариант. Языковой инструментарий должен предлагать такую же функциональность для абстрактных представлений, иначе с ним будет сложно работать. Теоретически, эта проблема, конечно же, решается. Более того, ее решение позволит видеть семантические отличия абстрактных моделей (когда можно явно увидеть, что две модели отличаются переименованным символом, а не догадываться об этом по изменениям в тексте.) Компания Intentional придумала для этого неплохой вариант решения, однако подождем их хвалить, пока не опробуем его в деле.

Но вернемся к положительным сторонам перехода на языковые инструментарии. Прежде всего, та комбинация нового специфического языка программирования и редактора, в котором это можно осуществлять, открывает путь к программированию для непрофессионалов. Более того, интеграция на уровне символов сводит на нет проблему разницы кода, написанного профессионалом и непрофессионалом. Может быть, мощные современные редакторы дадут возможность преодолеть проблему языка COBOL, так как предоставят непрограммистам среду, специально настроенную на нужды их работы.

Да, пожалуй, возможность привлечения к программированию знатоков предметной области – самая захватывающая из всех, связанных с языкоориентированным программированием. Похоже, что когда мы снова и снова говорим о том, что какое-то приложение серьезно увеличивает продуктивность работы программиста, мы пытаемся оптимизировать пустой цикл. В абсолютном большинстве проектов, с которыми мне доводилось сталкиваться, самой большой проблемой было общение между программистами и людьми, для которых они писали программное приложение. Если решить эту проблему, можно достичь больших успехов даже без самых лучших технологий. А если эта проблема стоит остро, то никакой Smalltalk вас не спасет.

Большинство энтузиастов языкоориентированного программирования говорят о привлечении к программированию знатоков предметной области. Я даже слышал о том, что секретарши будут программировать на внутренних DSL, созданных на Lisp. К сожалению, по опыту известно, что подобные утверждения еще ни к чему не приводили. Может быть хоть в этот раз мы сможем наконец сделать это – ведь теперь появилась возможность объединить достоинства специфического для данной предметной области языка и мощного инструментария, в котором этот язык можно редактировать. Если это получится, то выгода от использования языкоориентированного программирования будет неизмерима. Я был просто поражен, когда узнал, насколько важным считают вовлечение в процесс непрограммистов Чарльз Симони и разработчики из Intentional Software, и как это влияет на все принимаемые ими решения.

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

Изменения в нашей концепции DSL

В этой статье я приводил довольно малоинтересные варианты DSL. Их было легче использовать, потому что их просто писать и удобно приводить в пример. Однако даже более сложный DSL для составления соглашений весьма условен – хорошо видно, как его можно представить в виде обычного текстового варианта языка. Многие рассчитывают на появление графических DSL, однако даже это не может продемонстрировать все их возможности. Самую большую опасность представляет сам термин «язык», который может скрыть от людей те возможности, которые скрывает в себе языковой инструментарий.

ПРИМЕЧАНИЕ

См. статью «A Language Workbench in Action – MPS», http://www.martinfowler.com/articles/mpsAgree.html#AgreementDsl

Когда мы с коллегами обсуждали конференцию OOPSLA 2004, больше всего разговоров шло о выступлении Джонатана Эдвардса, который рассказывал о «Примеро-центрическом программировании» (Example Centric Programming). Главным в этой методологии является редактор, который отображает не только программный код, но и результаты выполнения фрагментов этого кода. Суть метода заключается в том, что нам легче думать о конкретных примерах даже тогда, когда мы манипулируем абстрактной моделью. Такая же склонность к примерам привлекает многих и к «Разработке, основанной на тестах» (Test Driven Development). Я думаю, это можно назвать Specification by Example (http://martinfowler.com/bliki/SpecificationByExample.html).

На основе своих идей Эдвардс создал собственное приложение под названием Subtext (http://subtextual.org/). Во многом оно напоминает языковой инструментарий (например, в отказе от текстовых исходных файлов). Subtext менее интересен с точки зрения создания новых языков, однако он дает понимание того, как может меняться мышление программиста с развитием языковых инструментариев, мышление, основанное на том, что язык и инструмент тесно переплетены между собой.

И это именно та причина, которая сможет помочь языкоориентированному программированию преодолеть опасности, погубившие планы разработчиков COBOL. Как я уже говорил выше, мы постоянно пытаемся изобрести технологии, которые позволили бы пользователям выступить в роли программистов. И нам это постоянно не удается. А теперь давайте обратимся к одной технологии, в которой нам удалось достичь этой цели – к электронным таблицам (spreadsheets).

Большинство программистов никогда не назовут работу с электронными таблицами программированием. И тем не менее, пользователи – непрофессиональные программисты – умудряются создать с их помощью весьма сложные системы. Электронные таблицы – это замечательная среда программирования, которая уже показала, какие свойства должны быть у инструмента, предназначенного для непрофессионального программирования:

  • Незамедлительная обратная связь с пользователем, включая немедленное отображение результатов вычисления.
  • Глубокая взаимосвязь инструмента и языка программирования.
  • Отсутствие текстовых исходных файлов.
  • Отсутствие постоянного отображения всей имеющейся информации – формула выводится только при редактировании ячейки, в которой она содержится. Во всех остальных случаях программа показывает ее значение.

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

Итак, когда мы говорим о работе с DSL в языковом инструментарии, мы должны думать не столько о тех примерах, которые я здесь приводил, и не о графических языках, которые так любят те, кто занимается моделированием приложений. Лучше представить себе, что языковой инструментарий станет следующим поколением электронных таблиц.

Заключение

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

С моей точки зрения, языковой инструментарий дает два основных преимущества. Во-первых, это большая продуктивность работы, возникающая за счет использования лучших инструментов. Во-вторых, это большая продуктивность работы, которую дает более тесное сотрудничество между программистами и людьми, знающими предметную область. Фактически, это возможность для непрограммистов непосредственно участвовать в разработке приложения. Конечно, понять, насколько реализовались эти преимущества, можно только по прошествии некоторого времени. Сам я думаю, что первое преимущество никуда не денется, программистам будет действительно удобнее работать с языковым инструментарием. Однако оно не окажет глобального влияния на всю программную отрасль. А вот если языковые инструментарии изменят отношения между программистами и экспертами предметной области – вот это уже будет иметь просто ошеломительный эффект. Но для этого языкоориентированному программированию надо учесть и преодолеть уроки COBOL.

И пожалуй, самое интересное – если мы будем использовать языковые инструментарии, то едва ли сможем себе представить, как будут выглядеть DSL будущего. До сих пор мое представление ограничивалось тем, как могут выглядеть текстовые или графические языки. Но при тесном взаимодействии редактора и схемы перед нами откроются такие возможности, какие и в голову не приходят сейчас программистам, пишущим внешние DSL. И если языковой инструментарий оправдает наши надежды, то уже через десять лет мы будем смеяться над тем, как мы представляли себе DSL.

Как я уже говорил, сейчас языковой инструментарий находится на самой ранней стадии развития. И только через несколько лет мы сможем по-настоящему опробовать, на что он способен. Не буду ничего говорить о том, насколько языковой инструментарий изменит всю парадигму программирования, как надеются его фанаты. Из меня вообще неважный предсказатель. Скажу только, что это – одна из самых интересных идей на горизонте современного программирования. И если разработчики языкового инструментария осознают весь потенциал своих приложений, это будет иметь огромный резонанс во всем, что связано с нашей профессией. И даже если этого не случится, я думаю, что их разработки подарят нам немало весьма и весьма интересных идей.

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

Дополнительные материалы:

Copyright © 1994-2020 ООО «К-Пресс»

Документация

Здесь вы найдете отсылки к руководствам, урокам и ссылкам, которые могут быть полезны, если вы почувствуете, что хотите программировать на Ruby.

Установка Ruby

Если вы хотите попробовать Ruby не только в браузере (смотрите ссылки ниже), но и локально – вам нужно установить Ruby на ваш компьютер. Вы можете проверить, установлен ли Ruby на ваш компьютер, набрав в терминале

Это должно вывести некоторую информацию об установленной версии Ruby. Если нет, смотрите страницу установки с различными способами получить Ruby.

С чего начать

Руководства


Документация языка

Редакторы и среды разработки

Для программирования на Ruby вы можете использовать стандартный редактор вашей операционной системы. Кстати, для более эффективного написания кода, стоит использовать редактор с поддержкой Ruby (например, подсветка синтаксиса, просмотр файлов) или среду разработки с продвинутыми функциями (например, code completion, рефакторинг, поддержка тестирования).

Далее следует список популярных инструментов, используемых рубистами:

Ruby FAQ

Содержание

TODO [ править ]

  • DRb
  • Capistrano
  • MySQL issues
  • Разворачивание приложения на сервере. Связки nginx(apache, pound) + mongrel, nginx(apache) + fastcgi. Различия и предпочтительные варианты.
  • Интро для новичков
  • (добавьте что-то своё)

Документация [ править ]

Какие книги можете порекомендовать для изучения Ruby, Ruby on Rails? [ править ]

  • Programming Ruby (Pickaxe, Кирка). Библия по языку, хотя и немного устарела. Первое издание в HTML
  • Agile Web Development with Rails (AWDwR). Библия по Рельсе, но устаревает быстрее, чем Кирка. Новичку, тем не менее, все равно будет полезна.
  • Ruby Way, Hal Fulton

Ещё есть разные Ruby Recipes, Rails Cookbook и проч. На английском языке почти все из них съедобны, но второстепенны по отношению к упомянутым.

Отдельной строкой: Why’s (Poignant) Guide to Ruby. Книга — шедевр, но на любителя.

Может мне кто-нибудь кинуть ссылку откуда можно скачать «Мега книжку»? [ править ]

Не может. Ищите в гугле. В группе ror2ru уважают чужой интеллектуальный труд (как бы банально это ни звучало).

Есть какие-либо бесплатные книги или документация в сети? [ править ]

  • Core API и стандартная библиотека Ruby, ссылки на книги.
  • Кирка, первое издание.
  • API Ruby on Rails (стабильной версии)
  • API Ruby on Rails (Edge). См. также ссылки на caboo.se.
  • why’s (poignant) guide to ruby Забавная книга
  • A Little Ruby (Draft) Несколько глав в стиле Little Schemer
  • Humble Little Ruby Book Для любителей chunky bacon
  • w:Ruby Русское описание Ruby на wikipedia
  • w:Ruby on Rails Русское описание RubyOnRails (там приведены ещё ссылки на русскоязычные документы по Rails)
  • Е.Охотников «Ruby-новые грани»
  • описание Ruby (ахтунг: очень вредные советы ins > Где купить русские переводы книг по Ruby и Rails? [ править ]

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

Русские переводы книг:

Видео с конференций, презентации [ править ]

Какие блоги почитать? [ править ]

Правда, что Ruby очень медленный язык? [ править ]

Есть разные реализации языка Ruby. Самая широкоиспользуемая на сегодняшний день — MRI (Matz Ruby Interpreter) 1.8.6 является пока что и самой быстрой. Также существует реализация Ruby 1.9 (YARV), JRuby (Руби на чистой джаве), Rubinius (реализация по типу Smalltalk-80, с динамической оптимизацией), IronRuby (версия для дот-нета). Первые три реализации имеют задатки для реального ускорения относительно MRI 1.8.6, но фактически заметной оптимизации в них пока нет (1.9 все-такие реально быстрее процентов на 10, но менее стабильная).

Причины тормозов Ruby 1.8.6

  • Интерпретация, а не использование байт-кода. После загрузки файла, он раскладывается в AST (Abstract Syntax Tree), который интерпретируется рубином. Никакой оптимизации, по типу той, которую делают компиляторы Си или Джавы тут нет. Это основная причина тормозов языка.
  • Динамичность. В Си вызов функции — это одна машинная операция перехода по заранее известному адресу. В Ruby вызов метода — это посылка сообщения и поиск ответного метода. Методы в руби многократно дороже, чем даже виртуальные методы в Си++, но на порядок быстрее, чем общее время интерпретации синтаксиса (см. пред. пункт)

Так ли все плохо?

Да, Ruby медленнее чем Python, Perl, PHP, C, Java, Erlang. Но это не всегда критично.

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

С другой стороны, бывают случаи, когда программа работает, скажем, в 100 раз медленнее, чем на Java, но она работает _достаточно быстро_ для имеющихся задач.

Кроме того, для рубина есть (и используются) удобные средства интеграции с Си, которые позволяют с легкостью выносить простые, но громоздкие для MRI задачи в Си и прикреплять с помощью динамических библиотек (.dll, .so, .bundle).

Есть примеры достаточно сложных и нагруженных систем, где работает Ruby.

В первую очередь, делают компиляцию в байткод. Это происходит в YARV и Rubinius, JRuby. Байткод хорош тем, что он прост и его можно оптимизировать и даже компилировать прямо в машинный код, который останется лишь выполнить. Сложность заключается в том, чтобы сохранить динамичность языка, поэтому эти системы не появляются за один день и существует немного примеров успешных реализаций (Smalltalk-80, Self-93, Strongtalk, как варианты).

Компенсация минусов MRI 1.8.x (Matz Ruby Interpreter) [ править ]

Как преодолеваются различного рода проблемы.

  • Скорость интерпретатора. Особенно сказывается в коде, производящим вычислительную работу (смайлик). Если есть задача что-то посчитать, обработать строку, разобрать XML и т.п., то лучше всего написать для этого C-extension (расширение на Си). Как это делается см. в Pickaxe 2nd ed. или в первой редакции. API MRI позволяет писать на Си практически тот же код, что и на руби благодаря использованию родных рубиновых структур (RArray, RString и т.п.) Даже если переписать код впрямую на т.н. Ruby-C (Си с использованием руби-структур), будет достигнуто серьёзно улучшение работы кода. Во-первых, благодаря тому, что не будет работать интерпретатор, во-вторых потому что вы будете использовать более эффективные примитивы (чистый int вместо Fixnum).

Иногда проще переписать имеющийся код на рубине так, чтобы его стало банально меньше. Так появился Merb, как альтернатива Rails. Основное его достоинство — в несколько раз меньшее время прохода запроса от сетевого порта до пользовательского кода («время диспатча»). Если пользовательский код сам по себе очень быстр, то время диспетчеризации играет большую роль в общем быстродействии. Также Мерб умеет многопоточность, что позволяет сэкономить память, не запуская лишние экземпляры веб-серверов для максимальной утилизации CPU. Рельса же не является «thread-safe».

  • Утилизация 100% CPU, когда требуется 400%. В MRI используются «зеленые потоки» (green threads). Их достоинство — легковестность и большая скорость в сравнении с нативными потоками (уровня ОС), недостаток — утилизация только одного ядра, блокирование всех потоков на операциях ввода-вывода, блокирование всех потоков при выполнении native-кода (в экстеншенах). В случае серверного софта, следует проектировать программу так, чтобы её можно было запускать в раздельных процессах. Например, на четырёх ядрах веб-сервер следует запускать как минимум в 4-х экземплярах и использовать балансирующий прокси-сервер для раздачи запросов между ними (например, nginx). Если веб-сервер не многопоточный, то количество экземпляров может быть и больше количества ядер (Рельса, например, не thread-safe, поэтому веб-сервер монгрел ставит все запросы к ней в одну очередь, блокируя мьютекс перед Dispatcher.dispatch!).
  • Блокирующий ввод-вывод. Существует библиотека EventMachine, которая избавляет от проблем с блокирующим вводом-выводом и предоставляет удобный унифицированный интерфейс для написания истинно event-driven приложений. Библиотека включает в себя реализации на чистом руби (не на всех платформах работает гладко), бандл на Си++ и версия для JRuby. Используется в проксирующем http-сервере Swiftiply. В проекте Rubinius стандартная библиотека использует libev (на Си) для эффективной работы с сетевыми событиями. Считается, что на рубиниусе EventMachine не нужна: стандартная библиотека достаточно эффективна.

Компенсация плюсов Ruby (бывает и такое) [ править ]

Чрезмерное увлечение созданием DSL (domain specific language), неоправданное использование динамики. Речь идет о *_eval, .method_missing, .send. Общее правило: не следует делать реализацию более умной и сложной, чем того требует задача. Хорошая цитата со страницы о Мербе:

Слишком «умный» код не только тяжел для понимания коллегами или самим писателем спустя некоторое время, но и приводит к ненужным затратам процессорного времени и оперативной памяти.

В частности DSL возникает когда становится очевидным некоторый паттерн. Пока вы не повторили некоторый код в точности пару-тройку раз, не имеет смысла его каким либо образом упаковывать в красивую оболочку метапрограммирования. belongs_to — это уже паттерн. Ваш код — ещё нет.

Кодировки [ править ]

  • Как успешно работать с Unicode? Уникод — слишком общее название. См. два варианта корректного вопроса:
  • Как успешно работать с кодировкой UTF-8? Читайте подробности ниже.
  • Как успешно работать с кодировкой XYZ? Хранить и обрабатывать данные в UTF-8, принимать и выдавать в той кодировке, которая нужна с помощью iconv (библиотека для одноименной программы).

Строки в руби не знают UTF-8?!

Строка в рубине по-определению — набор байт. Все операции типа string[0,3] управляют байтами, а не символами. Многие библиотеки рассчитывают работать с байтами, поэтому переопределение таких методов с тем, чтобы они понимали UTF-8 — неверный путь. В библиотеке activesupport существует модуль Multibyte (благодарности — Юлику Тарханову), который добавляет к строкам метод chars («прокси» для операций над utf-8-символами).

Чеклист

  • $KCODE = ‘u’; require ‘jcode’ включает «юникод-режим» для ряда методов (они начинают работать с символами): chop!, chop, delete!, delete, squeeze!, squeeze, succ!, succ, tr!, tr, tr_s!, и tr_s. Библиотека jcode довольно ограниченная. Рекомендуется использование activesupport и String#chars
  • require ‘rubygems’; require ‘active_support’
  • в my.cnf (если у вас mysql) везде, где требуется, прописать utf8 (если, разумеется, есть доступ к нему).
  • в database.yml указать encoding: utf8
  • в каждой (!) миграции (для MySQL): create_table «some_models», :options => ‘ENGINE=InnoDB DEFAULT CHARSET=utf8′ (вместо InnoDB — ваш любимый тип таблиц)
  • в environment.rb — config.active_record.schema_format = :sql (потому что в schema.rb не сохраняются объявления кодировок — тесты будут поломаны)
  • mysqldump . —default-character-set=utf8 . — чтобы не получить черт-те что, если в my.cnf не прописана utf-8 в умолчаниях для базы и таблиц.

Что такое символы? или :wtf [ править ]

Русскоязычный комментарий к нему:

  • Символ — это не строка. Это «число с именем».
  • Наиболее корректное понимание символа на низком уровне — «автоматический enum в Си».

Утечка памяти

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

Можно превращать символы в строки (инспектировать символ):

  • Здесь вы создаете объект класса String, который будет уничтожен мусорщиком.

Можно превращать строки в символы (генерировать символ):

  • Здесь вы создаете объект класса Symbol, который не будет уничтожен мусорщиком.

Общее правило: никогда не превращать вводимые пользователем строки в символы.

Возможная атака: в приложение вводят различные строки, которые превращаются в символы. Расход памяти растет. При достижении максимального числа символов (приблизительно 2 млрд), происходит bus error или segmentation fault.

ERb [ править ]

ERb (Embedded Ruby) — Встроенный Руби.

ERb — это вот что:

Где используется ERb?

  • XML/HTML-шаблоны в Rails
  • фикстуры в Rails (test/fixtures/*.yml)
  • где угодно ещё, где вы пожелаете прогнать некий текст через шаблонизатор.

Есть 3 известные реализации ERb:

  • Собственно, ERb. Входит в состав стандартной библиотеки MRI 1.8.*
  • eRuby. Реализация на Си.
  • Erubis. Реализация на Руби, самая быстрая на сегодняшний день (умеет компиляцию и кеширование шаблонов).

IRb [ править ]

IRb = Interactive Ruby.

Наберите в терминале irb. Вы увидите консоль, в которой можно набирать код на руби и сразу видеть результаты его выполнения.

Рубинисты экспериментируют в консоли, если хотят изучить малопонятный код. Не стоит спрашивать товарища «почему не работает?!», пока вы не поигрались с проблемным кодом в irb.

Полезные опции

  • irb -rsomething делает require ‘something’ (как и ruby -r)
  • irb —prompt simple включает компактное приглашение:
  • irb —prompt xmp выключает всякое приглашение (удобно для копирования кода из консоли в текстовый редактор):

IRb в Rails

script/console открывает irb —prompt simple и загруженным environment.rb

Ruby on Rails [ править ]

Что такое EDGE и с чем его едят? [ править ]

Едж — это по-английски «край». Последняя ревизия в репозитории. Когда говорят «сижу на едже», это означает, что используется не некоторая зафиксированная стабильная версия, а регулярное обновление библиотеки самыми последними ревизиями.

В первом утверждении есть доля лукавства. На самом деле, «стабильная ветка» тоже регулярно обновляется. Но туда не идут новые возможности — только исправления ошибок. Таким образом, едж — это свежий срез «девелоперской ветки».

Едж хорош тем, что туда попадают разные полезные шутки, которые всем разработчикам сразу хочется использовать. Полушутка. Некоторые важные исправления не всегда идут в обновление стабильных веток, но зато появляются в едже. Сейчас Core team достаточно организован, и код новой ревизии, как правило, не ломает серьёзно ваше готовое приложение.

Если вы не покрываете свой код тестами на 99,99%, то использовать едж-рельсу вам будет очень страшно.


Если вы держите свой репозиторий в subversion и хотите регулярно обновлять рельсу, вам пригодится Piston.

Если вы используете Git, то можете просто вычекнуть рельсу в vendor/rails или поступить хитрее — выделить её в отдельный репозиторий, обновлять там с помощью git-svn, а внутри своего репозитория подключить git-рельсу через git-submodules. (Спросите Юру yrashk Рашковского и Олега oleganza Андреева, как это делается :-)

Если вы хотите следить за изменениями по RSS, вам сюда: http://dev.rubyonrails.org/timeline/changeset — настраиваете что вам нужно и подписываетесь (внизу страницы). Если лень настраивать самому, в рассылке проскакивала такая ссылка: http://dev.rubyonrails.org/timeline?changeset=on&max=30&daysback=30&format=rss

Плагины [ править ]

Плагин это механизм для расширения функциональности Rails фреймворка. Каждый плагин лежит в каталоге vendor/plugins и должен имееть init.rb файл, который подключится при запуске вашего Rails приложения.

Как установить плагин?

Можно использовать стандартный инструмент Rails:

Если вы используете svn, то можно добавить ключ -x, тогда плагин поставится как svn-external. Таким образом, когда вы будете делать svn up, плагины будут обновляться из внешнего репозитория.

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

Если требуется установить плагин, скаченный через Piston (запустить правильно install.rb), то можно воспользоваться патчем или просто заменить содержимое файла script/plugin кодом, добавляющим поддержку Piston.

Ресурсы, CRUD, REST [ править ]

Разворачивание на сервере (deploy) [ править ]

(глючный, встречается на шаред-хостингах)

(вроде, самый глючный)

(оптимальный вариант, пока одного монгрела хватает)

Рельса — не может работать в несколько потоков (а MRI не умеет системные потоки, т.е. не более 100% CPU), поэтому для масштабирования запускается N-ое количество монгрелов на каждой машине. Запросы к ним приходят от балансировщика.

Проверенные балансировщики: nginx, apache, lighttpd. Рекомендуется nginx как наиболее стабильный, легкий и удобный в настройке.

Количество монгрелов не должно быть слишком большим, чтобы зазря не сжирать память и не тратить время процессора на планировщик задач, но и не должно быть слишком малым чтобы обеспечить необходимую пропускную способность. Как правило, количество бекендов (backend, в данном случае — монгрел) наращивают по мере необходимости и до тех пор, пока производительность не перестанет расти. Когда это случится, нужно думать об оптимизации приложения и/или устанавливать дополнительный физический сервер.

Средства разработки [ править ]

Какие средства разработки на Ruby (и с поддержкой Rails) существуют? [ править ]

Руби имеет такую особенность, что IDE ему обычно не нужна. Некоторые считают, что из-за отсутствия статической типизации, нормальной IDE для него не будет и это не повод для гордости. На самом деле, бывают довольно умные IDE и для Руби, но опять-таки, не всем они нужны.

  • IDE:
    • RubyMine − наверное лучшая IDE. Кроме специфических Rails фич поддерживает кучу гемов широко используемых Rails разработчиками типа RSpec, Capistrano, HAML, SASS, LESS, CoffeeScript. Для коммерческого использования стоит $69 в год. 30 дней триала.
    • NetBeans − хорошая IDE, но для нормальной работы нужен быстрый компьютер. По воле Oracle, плагин для Руби и рельсов больше не обновляется. RIP.
    • IntelliJ IDEA, с плагином для Ruby. См. п.1. База та же. Стоит от $199 в год. Крайне юзабельна при разработке под JRuby, когда часть когда пишется или переписывается на Java или других JVM языках, типа Scala или Closure.
    • Aptana Studio — Бесплатна, Open Source. IDE на базе Eclipse(со всеми его преимуществами и недостатками) поддержка ruby вторична, но в целом работает. Выгодно отличается наличием view с терминалом прямо в IDE. Сахара в виде авторефакторингов для Eclipse based IDE, на удивление мало.
  • Часто используемые текстовые редакторы:
    • TextMate (Mac OS X only) — де-факто стандарт Rails тусовки. Стоит $53. 30 дней триала.
    • Sublime − Улучшенный и кроссплатформенный клон TextMate, стоит $59, но, а-ля Total Commander, покупки не требует.
    • Vim + rais.vim − И этим всё сказано.
    • emacs − см. пункт выше
    • E-TextEditor − (почти TextMate для Windows)

От простых текстовых редакторов они отличаются в первую очередь тем, что у них есть бандлы к разным функциям Руби, Рельсы, РСпека. (Автозаполнение, запуск типичных скриптов.)

Тестирование [ править ]

Какие средства для тестирования Ruby приложений существуют? [ править ]

  • В стандартной поставке: require ‘test/unit’ (Test::Unit)
  • Чудо-библиотека для Behaviour Driven Development: RSpec (и «rspec on rails»)
  • Дебаггер ruby-debug.
  • Многообещающий Cucumber (тоже BDD)

Хостинг [ править ]

VPS хостинг [ править ]

VPS/VDS, виртуальные выделенные сервера. Подойдет любой, на котором соберется Ruby. Расчитывайте на то, что один экземпляр сервера Mongrel может занимать до 70 МБайт (обычно 30–40 МБайт, число зависит от используемых библиотек и их «текучести»), а таких серверов на одно приложение потребуется несколько.

Shared хостинг [ править ]

  • http://www.textdrive.com — shared rails хостинг, некогда рекомендуемый официально автором Rails.
  • http://www.dreamhost.com — уже успел испортить себе репутацию :)

В России

  • http://www.bhost.ru — shared Ruby on Rails хостинг на Mongrel.
  • http://locum.ru — Rails ориентированный хостинг-провайдер. (Nginx+Passenger, git, capistrano, популярные gem’ы)

Правила списка рассылки ror2ru [ править ]

Изначально группа ror2ru создавалась для становления русскоязычного сообщества Ruby on Rails.

  • адекватная помощь начинающим,
  • обмен опытом по программированию на Ruby, Ruby on Rails (и др.),
  • обмен опытом по развёртыванию приложений на базе Ruby (веб-серверы и т. п.),
  • обмен опытом вообще в разработке (веб-)приложений (архитектура, производительность, оптимизация и т.п.),
  • постинг интересных событий в рунете (конференции по RoR, доклады, выход книг на русском и т.п.),
  • постинг интересных событий в сфере Ruby/Rails,
  • информирование о новых проектах на Ruby/Rails (в рунете) — это важный момент, т. к. для интересующихся новой технологией, как правило, является критичным наличие работающих приложений, и чем красивее, и интереснее они выглядят, тем лучше :),
  • поиск и предложение работы.
  • (ваше предложение?)

Репозитории Ruby DSL (Domain Specific Language), примеры

Я ищу отличные примеры Ruby DSL (Domain Specific Languages). Какие репозитории, проекты, которые вы знаете, стоит прочитать? Почему это (или: они) отличные примеры?

Мне особенно интересны более сложные примеры, которые хорошо продуманны и разработаны.

Rake and Rack — неплохие примеры DSL. Если вам нужны еще несколько примеров, проверьте их:

  • Sinatra — очень популярный DSL для создания веб-приложений, и он открывает исходный код на GitHub.
  • Twibot — это новый DSL, вдохновленный Sinatra, который позволяет создавать ботов Twitter, которые автоматически отвечают на сообщения и ответы.

Если вы хотите начать свой собственный, вот отличный учебник под названием Построение DSL в Ruby.

В области Behavior-Driven Development вы можете проверить:

  • Cucumber — Опишите BDD, используя сценарии
  • RSpec — Замените тестовый код указанием поведения.


Хотя я должен признать, что код RSpec оставляет меня иногда царапать мою голову, потому что я все еще очень начинающий.

Другим примером, конечно, является Rake, система сборки Ruby. Что делает DSL «хорошим» на мой взгляд:

  • Обозначение соответствует значению, т.е. если вы читаете предложение (утверждение) в DSL, у вас есть четкое, однозначное представление о том, что он делает.
  • Домен, т.е. DSL не решает каждую проблему во Вселенной, а фокусируется на одном небольшом домене (таком как создание программного обеспечения, запрос данных или создание пользовательских интерфейсов).
  • Высокий уровень абстракции. DSL использует концепции высокого уровня, которые может использовать программист, и переводит их на более низкую реализацию (внутренне). В случае с Rake основной концепцией, на которой основан язык, являются задачи и зависимости между ними.

Вы проверили Docile gem, это может быть самый простой и самый чистый способ удовлетворить ваши потребности?

Технология Клиент-Сервер 2005’3

Home Поиск Издания Контакты k-press.ru

Автор: Мартин Фаулер
Перевод: А.Максимова

Опубликовано: 26.02.2006

Новые идеи в области разработки программного обеспечения, как правило, представляют собой лишь вариации на тему старых. В этой статье я расскажу об одной из таких идей, а именно о создании приложений, которые я называю «языковым инструментарием»: Intentional Software, Meta Programming System, которую разрабатывает компания JetBrains и Software Factories, которые делают в Microsoft. Все эти приложения используют давно известный подход к разработке программного обеспечения — назовем его «языкоориентированным программированием». Благодаря использованию в них инструментария современных сред разработки (IDE), этот вид программирования становится гораздо более жизнеспособным. Как бы там ни сложилось в будущем, я уверен, что этот вид приложений на настоящий момент является самым интересным явлением на горизонте нашей индустрии. И уж достаточно интересным для того, чтобы написать это эссе и попробовать изложить хотя бы в общих чертах их суть и возможную выгоду от их использования.

Давным-давно существует такой стиль программирования, при котором программные приложения пытаются описывать с помощью языков, специфичных для соответствующей предметной области. В Unix это «малые языки», которые генерируют программный код посредством lex и yacc; в Lisp такие языки разрабатывают внутри самого Lisp, чаще всего используя его макросы. Однако такой стиль программирования пока не нашел широкого применения и популярен только среди его ярых сторонников, к немалому их огорчению.

Последние несколько лет можно было наблюдать за попытками создать новый вид программных систем, которые как раз и призваны поддерживать «языкоориентированный» стиль программирования. Самая ранняя (и наиболее известная) из них – это Intentional Programming, концепция которого была разработана Чарльзом Симони (Charles Simonyi) еще в Microsoft. Но в этой области работают и другие исследователи, и вот как раз их деятельность вызвала новую волну интереса к данной области.

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

В этой статье всего два новых базовых термина: «языкоориентированное программирование» (Language Oriented Programming) и «языковой инструментарий» (Language Workbench). Языкоориентированное программирование – это общее название для стиля разработки программного обеспечения, который строится вокруг использования ряда языков, специфичных для данной предметной области. Языковой инструментарий – это общий термин для всех видов приложений, разработанных для поддержки языкоориентированного программирования. Иными словами, языковой инструментарий дает возможность заниматься каким-то одним видом языкоориентированного программирования. Возможно, вам незнаком термин » язык предметной области» (Domain Specific Language), чаще передаваемый аббревиатурой DSL. Это урезанная форма языка программирования, созданная специально для решения определенного вида проблем. В некоторых сообществах программистов под DSL принято понимать только языки, создаваемые для решения конкретной задачи, но я предпочитаю использовать термин DSL для описания языков, которые могут использоваться для решения некоторого ограниченного класса проблем.

Хотелось бы кратко описать ситуацию, сложившуюся в настоящий момент в сфере языкоориентированного программирования, показать на примере, что это такое, а также рассказать о всевозможных течениях, которые существуют в этой области, а также спорах и аргументах «за» и «против». Конечно, если вы уже наслышаны о языкоориентированном программировании, вам захочется пропустить эту часть. Однако я уже убедился, что много (можно даже сказать, абсолютное большинство) программистов не так уж хорошо в этом ориентируются. Давайте же разберемся сначала с общей концепцией, а потом перейдем к тому, что такое языковой инструментарий, и как существующие его разновидности могут изменить отношение к языкоориентированному программированию в целом.

Пока я писал, выяснилось, что для одной статьи материала слишком много. Пришлось выделить некоторые части в отдельные небольшие статейки. Далее в тексте я буду указывать, когда стоит прерваться и переключиться на дополнительную статью. Ссылки на них вы найдете в тексте статьи. Обратите особое внимание на «Пример использования MPS» (http://martinfowler.com//articles/mpsAgree.html) – там я показываю, как создать DSL с помощью одного из существующих языковых инструментариев. Мне кажется, это лучший способ почувствовать, что будут представлять собой подобные приложения в будущем. Впрочем, сначала вам нужно обратиться к общему описанию, иначе от конкретных примеров будет мало толку.

Простейший пример языкоориентированного программирования

Начну с самого простого примера языкоориентированного программирования и типичной ситуации, когда оно может понадобиться. Представьте, что нужна система, которая считывает файлы и создает на их основе некие объекты. В каждой строке файла хранятся данные для создания одного объекта. Эти объекты могут принадлежать разным классам, соответствующий класс обозначается четырехзначным кодом в начале строки. Далее в строке идут данные для полей класса, причем поля у разных классов могут быть разными. Поля определяются скорее своей позицией, нежели разделителями. Так, идентификатор клиента может располагаться между 4-ым и 8-ым символом записи.

Вот пример таких данных:

Точки здесь обозначают какие-то ненужные и неинтересные данные. Закомментированная строка сверху показывает расположение символов. Первые четыре символа обозначают тип данных – SVCL – Service Call, USGE — Usage. Символы с 5-ого по 18 после SVCL указывают имя клиента.

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

У меня для этого существует очень простой класс. Он может быть параметризован коллекцией классов-стратегий для считывания объектов определенного типа. Для данного примера понадобится всего две стратегии – одна для Service Call, другая – для Usage. Стратегии я сохраняю в ассоциативном массиве (map), ключом для которого служит строковый код.

Вот код для обработки файла:

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

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

Экстракторы полей можно добавить в стратегию уже после ее создания.

При обработке строки стратегия создает целевой класс и использует список экстракторов для заполнения его полей.

Экстракторы, в свою очередь, берут значения из соответствующей позиции в строке и проставляют их в поле созданного целевого объекта с помощью механизма reflection.

Пока что для решения нашей задачи я описал очень простую библиотечку. В сущности, я построил некую абстракцию, которую могу использовать для решения вполне конкретных задач. Для использования этой абстракции я должен создать ряд стратегий и указать их в Reader. Вот два примера того, как это может быть сделано.

Посмотрим на эти два различных стиля кодирования. Классы Reader и ReaderStrategy описывают абстракции, в то время как последний фрагмент кода – чистой воды конфигурация. При создании подобных библиотек очень часто бывает удобно думать в этих двух плоскостях – абстракция и конфигурация. Абстракция может представлять собой библиотеку классов, фреймворк для разработки, или просто набор вызовов функций. Абстракцию можно легко использовать в нескольких проектах, хотя часто такой необходимости и не возникает. С другой стороны, код конфигурации специфичен для проекта, довольно прост и прямолинеен.

Так как конфигурация довольно проста и, скорее всего, будет меняться чаще, чем абстракция, ее отделяют еще дальше. В нашем примере ее можно вообще вычленить из C#-кода и перенести в XML-файл (сейчас это модно).

XML – очень полезная вещь, но его не очень легко читать. Легче было бы показать, что имется в виду, с помощью собственного синтаксиса. Например, вот так:

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

Как видите, в результате получился очень маленький язык программирования, пригодный (исключительно) для отображения полей фиксированной длины на классы. Это классический пример традиционных ‘малых языков’ UNIX (http://martinfowler.com//articles/languageWorkbench.html#unixLittleLanguage). Для данной задачи это и будет язык предметной области, то есть DSL.

Этот язык хорошо демонстрирует сразу несколько характерных для DSL-языков черт. Во-первых, его можно использовать лишь для очень ограниченного числа задач, ведь он умеет только отображать некие записи фиксированной длины на классы. В результате этот DSL получился очень простым – в нем нет никаких управляющих структур или чего-то подобного. Он даже не является полным по Тьюрингу. Вы не сможете написать целое приложение на этом языке – все, что он позволяет, это описать малюсенький аспект целого приложения. Соответственно, чтобы сделать хоть какую-то законченную работу, этот DSL должен быть объединен с другими языками. И тем не менее, простота языка означает простоту его редактирования и транслирования (ниже я более подробно рассмотрю различные «за» и «против» DSL)

Посмотрим теперь на XML-вариант. Является ли он DSL? Я бы сказал, что да. Он использует XML в качестве синтаксиса, но по-прежнему остается DSL и во многом разделяет свойства языка из предыдущего примера.

Сейчас самый подходящий момент дать понятие разграничения, с которым всегда сталкиваешься, когда говоришь о языках программирования: а именно различие между абстрактным и конкретным синтаксисами. Конкретный синтаксис языка – это синтаксис, который мы видим, когда смотрим на код. Конфигурации, выраженные через XML и через наш собственный формат файлов, имеют различные конкретные синтаксисы. Однако и тот, и другой используют сходную структуру – существует ряд отображений, для каждого из которых заданы строковый код, имя целевого класса и набор полей. Эта базовая структура является абстрактным синтаксисом . Когда разработчики размышляют над синтаксисом языка программирования, они, в большинстве случаев, не делают такого разграничения. И тем не менее, оно играет большую роль при использовании DSL. Об этом можно думать двояко: либо у вас есть один язык с двумя конкретными синтаксисами, либо два языка с одним и тем же абстрактным синтаксисом.

Таким образом, этот пример приводит к интересному вопросу проектирования: что лучше — собственный конкретный синтаксис для DSL или конкретный синтаксис XML? Возможно, синтаксис XML легче читать программно, поскольку для XML существует множество библиотек и инструментов. С другой стороны, приведенный выше синтаксис проще. Готов поспорить, что его гораздо легче прочитать глазами (по крайней мере, в данном случае). Но как бы там ни было, наш случай не может изменить общее соотношение «за» и «против» DSL. Да и вообще, вы всегда можете смело утверждать, что любой конфигурационный XML-файл на самом деле является DSL.

Давайте сделаем шаг назад и обратимся к конфигурационному коду на C#. DSL это или нет?

Этот кусок кода похож на код на C#. Те, кто знает о моих предпочтениях в языках программирования, могут предположить, что последний пример – это код на Ruby. На самом же деле это точный «моральный эквивалент» примера на C#. Он куда больше похож на DSL благодаря различным особенностям Ruby: ненавязчивому синтаксису, использованию специальных конструкций для выражения диапазонов литералов и гибкостью в runtime-вычислениях. Это полный конфигурационный файл, который может быть считан и обработан в области видимости экземпляра объекта в runtime-е. Но это по-прежнему чистый Ruby-код, который взаимодействует с кодом фреймворка через вызовы методов mapping и extract, соответствующих AddStrategy и AddFieldExtractor в примере на C#.

Я бы сказал, что оба примера – и на C#, и на Ruby – представляют собой DSL. В обоих случаях используется подмножество возможностей основного языка и реализуются идеи, которые выражаются через XML или специальный синтаксис. По сути, DSL внедряется в основной язык, используя его подмножество как особый синтаксис для абстрактного языка. В общем-то, это скорее вопрос отношения к проблеме, чем что-либо другое. Я же решил смотреть на код C# и Ruby через призму языкоориентированного программирования. Кстати, у этой точки зрения глубокие корни – именно так Lisp-программисты создают DSL внутри этого языка. Конечно, то, что волнует авторов таких внутренних DSL, будет отличаться от того, что волнует программистов, пишущих внешние DSL. И тем не менее, общего между ними довольно много (позже я рассмотрю этот вопрос подробнее).

Теперь, когда я привел пример DSL, мне будет проще дать определение языкоориентированному программированию. Языкоориентированное программирование описывает разрабатываемые системы при помощи всевозможных DSL. В системе, где лишь малая часть функциональности написана на DSL, языкоориентированного программирования мало. А там где на DSL написана большая часть функциональности, языкоориентированного программирования много. Сложно понять, много или мало языкоориентированного программирования у вас в системе, особенно если вы используете внутренние DSL. И здесь, как и в случае с любым другим повторно используемым кодом, вы можете написать DSL сами, или же взять какие-нибудь чужие.

Традиции языкоориентированного программирования

Как видите, языкоориентированное программирование – вещь далеко не новая, и программисты уже давным-давно используют его в работе. Давайте же обратимся к текущему положению дел в этой отрасли, а потом уже посмотрим, какие изменения в картину внесут языковые инструментарии.

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

«Малые языки» Unix

«Малые языки» UNIX являются наиболее очевидными представителями мира DSL. Эти языки представляют собой внешние DSL-системы, которые, как правило, используют для трансляции встроенные инструменты Unix. В бытность мою студентом, я нередко забавлялся с lex и yacc (инструменты такого рода являются стандартными программами Unix). С их помощью гораздо легче писать парсеры и генерировать код (зачастую на C) для малых языков. Awk – еще один пример такого мини-языка.

Lisp представляет собой, пожалуй, самый яркий пример существования различных DSL прямо в самом языке. Символьная обработка включена и в название, и в повседневную работу «лисперов». Этому способствуют минималистический синтаксис Lisp, его замыкания (closures) и макросы (программисты часто при упоминании макросов представляют себе макросы в стиле С/C++; макросы в Lisp не имеют к ним практически никакого отношения – они функционируют на синтаксическом уровне и являются средством метапрограммирования, в С/C++ их можно сравнить с метапрограммированием на шаблонах, но макросы Lisp более мощные и гибкие – прим. ред.). Все это является отличной закваской для создания DSL. Пол Грэхем много пишет об этом стиле программирования (http://www.paulgraham.com/progbot.html). Похожие традиции разработки давно существуют и в языке Smalltalk.

Активные модели данных

Программисты, создающие сложные модели данных, могут продемонстрировать, как регулярно меняющиеся части системы могут быть закодированы с помощью информации в таблицах базы данных (их еще называют «таблицами с метаданными», а программы – «программами, управляемые таблицами»). В самой программе эти данные определенным образом интерпретируются и определеют поведение системы.

На самом деле в этом случае мы имеем дело с DSL, конкретный синтаксис которого содержится в таблицах базы данных. Довольно часто содержимое этих таблиц редактируется через GUI. Как правило, люди, которые с этим работают, даже не задумываются о создании какого-либо языка, а сложность работы с реляционным конкретным синтаксисом не позволяет таким языкам чрезмерно разрастаться и терять первичную направленность.

Адаптивные объектные модели

Поговорите с матерыми ООП-программистами, и они поведают вам о созданных ими системах, которые построены на основе компоновки различных объектов в гибком и мощном программном окружении. Такие системы строятся для сложных моделей предметной области, когда большая часть поведения системы определяется набором объектов, объединенных общей конфигурацией. Такой механизм позволяет строить различные сложные системы сходным образом. Сторонники OO-подхода используют адаптивные объектные модели (www.adaptiveobjectmodel.com) как адаптивные модели данных «на стероидах».

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

Конфигурационные файлы XML

Никто не возразит, когда вы заметите, что в любом современном Java-проекте меньше Java, чем XML. Крупные системы задействуют сразу несколько программных каркасов, большинство из которых используют для конфигурации XML-файлы. По сути, эти файлы представляют собой DSL. XML-файлы легко читаются программно, хотя человеку может оказаться куда проще прочитать файлы в специально созданном формате. Для тех, кому угловые скобки режут глаз, разработчики даже пишут специальные IDE-плагины для работы с XML-файлами.

Инструменты для построения пользовательских интерфейсов (GUI builders)

Вместе с появление графических пользовательских интерфейсов появились инструменты, позволяющие создавать такие интерфейсы путем перетаскивания элементов управления. Visual Basic, наверное, самый известный из таких инструментов, хотя я пользовался похожими программами для символьных терминалов задолго до того, как в моду вошли графические интерфейсы. Такие программы либо сохраняют информацию о расположении элементов управления в собственном формате и генерируют пользовательский интерфейс во время выполнения программы на основе этой информации, либо пытаются заложить всю требуемую информацию в сгенерированный код. Хотя внешне такой стиль работы с графическим интерфейсом выглядит весьма заманчиво (особенно, если посмотреть рекламные демо-ролики), у него есть определенные ограничения. Причем ограничения эти настолько существенны, что многие опытные разработчики интерфейсов предпочитают вообще не пользоваться такими инструментами для создания мало-мальски сложных приложений.

Инструменты для построения пользовательских интерфейсов представляют собой разновидность DSL, однако здесь редактирование кода существенно отличается от редактирования кода в текстовых программных языках, к которым мы так привыкли. Даже сами их разработчики часто не рассматривают созданные им приложения для построения интерфейсов как языки программирования. Некоторые считают, что в этом и состоит основная проблема всех подобных инструментов.


Языкоориентированное программирование: за и против

Итак, мы видим, что существующие виды и формы языкоориентированного программирования довольно популярны. Мне кажется, что описывая этот вид программирования в целом, будет удобно разделить все его разновидности на две основные категории: внешние и внутренние DSL. Внешние DSL пишут не на том языке, на котором написано все приложение, а на каком-то другом, после чего такой DSL трансформируется в приложение с помощью компилятора или интерпретатора. К таким DSL относятся «малые языки» Unix, активные модели данных, конфигурационные XML-файлы. Что касается внутренних DSL, то они превращают в DSL сам основной язык приложения. Лучшим примером такого языка служит Lisp.

Термины «внешний DSL» и «внутренний DSL» были придуманы специально для этой статьи. Дело в том, что мне так и не удалось найти в существующей литературе подходящую пару терминов, которые бы хорошо описывали эту разницу в DSL. Внутренние DSL иногда называют «встроенными» (embedded), но я бы не рекомендовал использовать этот термин, потому что его можно отнести к языкам, встроенным в приложение (например, встроенный в Word язык VBA, который можно посчитать разве что внешним DSL). Однако если вы обратитесь к другим источникам, то наверняка встретите термин «встроенный DSL».

Плюсы и минусы внутренних и внешних DSL настолько отличаются друг от друга, что лучше рассмотреть каждый вид этих языков отдельно.

Внешние DSL

Внешними я называю те DSL, которые написаны на языке, отличном от основного языка программного приложения. Примерами DSL такого типа могут служить «малые языки» Unix и конфигурационные XML файлы.

Самый большой плюс внешних DSL состоит в том, что их можно писать так, как вам вздумается. Иначе говоря, вы можете выразить предметную область в самой простой и доступной для чтения и редактирования форме. Формат такого DSL будет ограничен лишь вашим умением создать транслятор, который сможет зачитать конфигурационный файл и выдать некий выполняемый код – как правило, уже на основном языке приложения.

Отсюда же следует и очевидный недостаток внешних DSL – вам придется писать этот самый транслятор. Если язык несложен, как в том примере, что я привел выше, это будет совсем нетрудно. Более сложный язык потребует больших усилий, но с этим тоже можно справиться. Существуют ведь и генераторы лексических анализаторов, и другие инструменты для создания компиляторов, которые облегчают работу со сложными языками. К тому же, в самих DSL заложена идея простоты и читабельности. Так, XML ограничивает форму DSL, но при этом делает его очень удобным для чтения.

Еще один существенный недостаток внешних DSL состоит в том, что у них нет того, что я называю «символической интеграцией». То есть, на самом деле, DSL никак не связан с основным языком приложения. Программная среда разработки базового языка, на котором пишется приложение, ничего не знает о нашем новом DSL. Сейчас, когда программные среды разработки становятся все более изощренными, эта проблема стоит довольно остро.

К примеру, захотелось нам переименовать свойства целевого класса в том примере, который я уже приводил выше. Во всех современных средах разработки автоматическим переименованием уже никого не удивишь. Однако это переименование не будет работать в коде DSL. Это и есть тот самый «символический барьер» между миром C# и DSL для отображения файлов. Такое отображение можно транслировать в С#, но этот барьер не позволяет манипулировать программой как единым целым.

Недостаток интеграции будет сказываться постоянно. Во-первых, где писать код на DSL? Конечно, можно обойтись и простым текстовым редактором, но после современных сред разработки такие редакторы выглядят слишком уж примитивно. Мне же нужен всплывающий список всех имен полей, да еще чтобы работало автодополнение слов (completion). Я хочу, чтобы перекрывающиеся интервалы для различных полей были подчеркнуты красной волнистой линией, обозначая ошибку. Однако для этого мне понадобится такой редактор, который понимал бы семантику моего DSL.

Ну хорошо, я могу прожить и без редактора, который понимает семантику языка. А как быть с отладкой? Мой отладчик сможет работать с C#-кодом, полученным для моего DSL, но он не сможет работать с исходным кодом самого DSL. Было бы здорово, чтобы иметь собственную полнофункциональную среду разработки для моего DSL! Ведь те дни, когда код писали в простых текстовых редакторах и пользовались простыми отладчиками, уже канули в Лету. Теперь мы живем в эру post-IntelliJ (см. врезку), и все изменилось.

Чаще всего против использования внешних DSL выдвигают обвинение в «языковой какофонии». Суть его в следующем: программные языки довольно сложно освоить, поэтому задействовать сразу несколько языков в одной программе – значит, чрезмерно усложнить ее. На самом деле это просто небольшое недоразумение, которое проистекает из непонимания сути DSL. Те, кто говорит о «языковой какофонии», представляют себе мешанину из программных языков общего назначения – картину, действительно, жуткую. Однако DSL куда ограниченнее и проще, чем языки общего назначения. К тому же они тесно связаны с предметной областью, и изучать их гораздо легче.

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

Многие говорят о том, что создавать DSL сложно. Проектирование новых программных языков – вещь непростая, а писать несколько языков для каждого проекта будет просто непосильной тяжестью. Опять-таки, это обвинение коренится в том же недопонимании сути DSL, в том что они коренным образом отличаются от программных языков общего назначения. Самая сложная часть задачи, как мне кажется — построить DSL на хорошей абстракции. Это самое главное. А дальше я не вижу большой разницы между проектированием DSL и проектированием API. Не думаю, что создавать DSL намного сложнее, чем спроектировать хороший API.

Многие считают, что самый большой плюс внешних DSL состоит в том, что они вычисляются во время выполнения. Это дает возможность поменять часто изменяющиеся параметры без повторной компиляции программы. Именно поэтому конфигурационные XML-файлы так популярны в мире Java. Впрочем не стоит забывать, что это верно только для статически компилируемых языков. Существует целый ряд языков, код которых может быть проинтерпретирован во время выполнения, так что для них это не плюс. Кроме того, растет интерес к смешению компилируемых и интерпретируемых языков, например, IronPython в .NET. Это позволяет выполнить внутренний DSL IronPython прямо в контексте системы, написанной на C#. Те, кто работают с Unix, часто смешивают С и С++ с различными языками сценариев.

Внутренние DSL

Плюсы и минусы внутренних DSL являются зеркальным отражением внешних. Здесь уже нет символьного барьера. Можно в полную силу пользоваться возможностями основного языка приложения и при этом задействовать все инструменты, которые есть для этого языка. В качестве примера внутренних DSL можно назвать Lisp и адаптивные объектные модели.

Но здесь мы сталкиваемся с довольно серьезной проблемой, которая состоит в существенных различиях между основными «языками с фигурными скобками» (C, C++, Java, C#) и языками типа Lisp, которые действительно хорошо подходят для создания внутренних DSL. Стиль внутренних DSL гораздо более приемлем в Lisp или Smalltalk, чем в Java или C# — и действительно, сторонники динамических языков часто указывают на это как на одно из основных их преимуществ. Сейчас мы наблюдаем, как это происходит с некоторыми сценарными языками — в качестве примера можно привести мета-программирование в Ruby (http://poignantguide.net/ruby/chapter-6.html) и его использование в Ruby On Rails (http://www.rubyonrails.org/).

Внутренние DSL ограничены синтаксисом и структурой основного языка приложения. Чем более динамичен язык, тем меньше он страдает от этого ограничения. Такие языки обладают минималистским синтаксисом (Lisp, Smalltalk, языки сценариев), и это делает их хорошей основой для DSL, в отличие от «языков с фигурными скобками». Вы уже имели возможность в этом убедиться, сравнив примеры на C# и Ruby. Большую роль при создании внутренних DSL играют замыкания (closures) и макросы. В C-подобных языках большая часть подобных механизмов отсутствует, но у них есть другие возможности для создания внутренних DSL. Одна из них – аннотации (атрибуты в C#).

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

Возможность предоставить DSL всю мощь основного языка программирования – палка о двух концах. Если вы свободно владеете базовым языком, все хорошо. Однако сильная сторона DSL как раз в том и состоит, чтобы программировать, не зная всех возможностей базового языка. Это и позволяет непрофессиональным программистам вводить данные предметной области непосредственно в систему. Внутренний DSL может усложнить этот процесс, ведь существует масса мест, где пользователь может зайти в тупик из-за недостаточной осведомленности о возможностях базового языка.

Можно посмотреть на это и с такой точки зрения: языки общего назначения предлагают множество языковых конструкций и возможностей. Ваш DSL использует только часть этих возможностей. Когда инструментов больше, чем нужно, это только замедляет работу – ведь придется разбираться в них, чтобы выбрать нужные. В идеале хочется иметь только то, что нужно для выполнения работы, ну разве чуточку больше. (Чарльз Симони обсуждает эту идею, используя нотацию степеней свободы, http://blog.intentionalsoftware.com/intentional_software/2005/05/notations_and_p.html)).

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

Внутренние DSL очень близки к языкам программирования, поэтому с их помощью будет сложно выразить нечто, для чего в языке программирования нет соответствующего механизма. Например, в корпоративных приложениях часто возникает понятие слоя (layer). По большей части слои можно определить с помощью пакетов в языке программирования. Однако через пакеты нельзя задать зависимости между слоями. То есть весь интерфейсный код можно разместить в пакете MyApp.Presentation, а бизнес-логику в MyApp.Domain, но вы не сможете через внутренний DSL задать такое ограничение, чтобы классы из MyApp.Domain не ссылались на классы из MyApp.Presentation. До какой-то степени это еще раз иллюстрирует ограниченность динамизма в общеупотребительных языках программирования – ибо подобные вещи были возможны в Smalltalk, где программист имел больший доступ к метаинформации.

(Для сравнения можно взглянуть на более сложный пример, который я писал на одном из таких динамических языков (http://martinfowler.com//articles/mpsAgree.html). Вряд ли я буду возвращаться к этому примеру, но если кто-то это сделает, то я обновлю раздел «Дополнительное чтение».)

И для не-программистов

Одна из тем, постоянно всплывающих в рассуждениях о языкоориентированном программировании — возможность создания кода непрофессионалами. Всевозможные эксперты в различных предметных областях могли бы программировать, используя соответствующие DSL. Впрочем, подобная цель стоит перед программированием уже давно. Многие были когда-то уверены, что ранние языки высокого уровня (Cobol, Fortran) положат конец профессии программиста, потому что пользователи смогут программировать на них сами. А мне кажется, что это просто «синдром Cobol», и все те технологии, которые якобы должны свести на нет профессиональное программирование, ни к чему подобному не приведут.

Независимо от «синдрома COBOL-а», порой действительно удается сделать так, чтобы пользователи вносили существенный вклад в написание программ. Вот один из способов: выделить некую часть проблемы (не очень сложную и имеющую четкие границы) и позволить пользователям программировать именно в этой части. Впоследствии такие области можно превратить в DSL. Кстати, подобные DSL могут быть весьма сложными – возьмите, к примеру, MatLab. Это довольно сложный DSL, выживающий именно потому, что он жестко сфокусирован на своей предметной области.

Преимущество внешнего DSL для использования непрофессионалами состоит в том, что можно смело отказаться от всех сложностей базового языка приложения, и создать DSL, который будет понятен и удобен пользователям. Это особенно важно для языков с ограничивающим синтаксисом. Однако даже если вы имеете дело с простыми языками, внутренние DSL будут менее удобны: пользователь может сделать то, что кажется логичным с точки зрения языка, но будет выходить за рамки DSL. В результате он получит странное поведение программы или таинственные сообщения об ошибках, что легко вгонит его в ступор.

Многие адепты языкоориентированного программирования описывают, как в скором будущем вся предметная логика системы будет создаваться пользователями, а программисты будут лишь писать для них необходимые инструменты поддержки, позволяющие компилировать такие программы. Конечно, это не означает конца профессии «программист», однако едва ли при таком раскладе их понадобится много (ведь эти самые инструменты поддержки можно будет использовать повторно). Кроме того, при таком сценарии исчезнут все проблемы взаимодействия между программистами и заказчиками, которые так осложняют процесс программирования в наши дни. Красивая идея, ничего не скажешь. И все же это очень напоминает «синдром Cobol».

Конечно, подключение непрофессионалов к программированию – весьма ценная вещь, но я не думаю, что это самый веский аргумент в пользу языкоориентированного программирования. Хороший DSL позволяет работать более продуктивно и профессиональному программисту, даже если этим DSL не смогут пользоваться непрофессионалы. Возможно, хороший DSL должен изначально писать профессиональный программист, а уже потом его будут уточнять и пересматривать эксперты в предметной области.

Использование труда непрофессиональных программистов – рискованная ставка, но и выигрыш тут большой. Я ни за что не поверю в разговоры о том, что новую технологию подхватит огромное число пользователей и тут же начнет писать программы не хуже профессиональных программистов. Но если бы это все же удалось, то выгоду от внедрения такой технологии трудно было бы переоценить. И дело вовсе не в том, что профессиональных программистов станет меньше (или не будет вовсе). Дело в том, что можно было бы исправить то ужасное состояние дел, которое наблюдается сейчас в области взаимодействия программистов и экспертов в предметных областях. А это, по моему мнению, является камнем преткновения для успешной работы над проектами.

Подведем итоги

С моей точки зрения, самое большое достоинство языкоориентированного программирования – это возможность использования DSL, а недостаток – необходимость создавать средства для их поддержки. Если писать внутренние DSL, то уменьшится необходимость в средствах поддержки. Однако вместе с этим уменьшатся и возможности самого DSL – на него будут наложены ограничения, связанные с базовым языком (особенно если вы пишете на C-подобных языках). Внешние DSL не ограничивают ваши возможности, однако требуют усилий по проектированию самого языка, созданию транслятора, а также некоего инструментария, который будет помогать вам программировать.

Именно поэтому языкоориентированное программирование так медленно распространяется: и внешние, и внутренние DSL имеют весьма ощутимые недостатки. Вот в результате мы и мучаемся ощущением того, что с помощью DSL можно работать куда эффективнее, только непонятно, как.

И это естественным образом подводит нас к теме языкового инструментария. Итак, по сути своей, эти инструменты должны предоставить программистам широту возможностей внешних DSL и при этом уничтожить «семантический барьер». Более того, они должны позволять с легкостью создавать вспомогательные инструменты, не уступающие современным средам разработки. В результате языкоориентированное программирование станет доступным для тех, кого до сих пор отпугивали связанные с ним неудобства.

Составляющие языкового инструментария

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

Одной из сильных сторон языковых инструментариев является то, как они изменяют соотношение между редактированием и компиляцией программы. По сути, они дают возможность редактировать не текст программы, а некое абстрактное ее представление. Давайте остановимся на этом чуть-чуть подробнее.

В обычном программировании мы редактируем текст программы с помощью текстового редактора. После этого мы при помощи транслятора делаем исходный код исполняемым, то есть превращаем текст в то, что компьютер сможет понять и исполнить. Трансляция может происходить во время выполнения, как, например, в скриптовых языках (Python, Ruby), или же представлять собой отдельный шаг, как в транслируемых языках типа Java, C# и С.

Рисунок 1: Традиционная схема компиляции программы.

Разобьем этот процесс на составляющие. На рисунке 1 изображен упрощенный процесс компиляции программы. Чтобы файл foo.cs стал исполняемым, надо запустить компилятор. Учитывая контекст нашей беседы, давайте разобьем весь процесс компиляции на два шага. Во время первого на основе текста из файла foo.cs компилятор строит абстрактное синтаксическое дерево (АСТ). Во время второго шага происходит обход этого дерева и формируется CLR байт-код, который затем помещается в скомпонованный файл (exe).

Более подробно о генерации кода из внешнего DSL читайте в статье Generating Code for DSLs (http://martinfowler.com//articles/codeGenDsl.html).

Можно сказать, что существует несколько разных представлений программного приложения, а компилятор осуществляет трансляцию между ними. Файл с исходным кодом представляет собой редактируемое представление программы – именно с ним мы работаем, когда хотим изменить что-то в программе. Кроме того, это также и вид хранения программы, потому что при необходимости мы будем обращаться к исходному коду. Когда мы запускаем компилятор, на первом этапе он создает отображение редактируемого файла в виде абстрактного представления программы (абстрактное синтаксическое дерево), а потом генератор кода превращает его в еще одно представление – исполняемое (CLR байт-код).

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

Абстрактное представление очень недолговечно – оно существует только во время работы компилятора. Используется оно с одной лишь целью – разделить процесс компиляции на два последовательных шага. Разумеется, эта недолговечность является одной из самых серьезных причин, по которым мы не можем добиться символической интеграции между внешними DSL. Каждый из таких языков будет проходить через собственный процесс компиляции, поэтому их абстрактные представления окажутся никак не связанными между собой. Все соединтся лишь на последней стадии – в сгенерированном коде, когда все ключевые абстракции уже утеряны.

Сложные современные среды разработки (IDE), которые появились после IntelliJ IDEA, существенным образом изменили эту модель. Когда среда разработки загружает файл, в ее памяти создается его абстрактное представление, которое помогает редактировать файл. (В языке Smalltalk было нечто похожее.) С помощью абстрактного представления файла мы можем делать такие простые вещи, как автодополнение имени метода, или же такие сложные как рефакторинг (автоматизированный рефакторинг представляет собой трансформацию абстрактной модели).

Один из моих коллег, Мэтт Фёммель, рассказывал, как однажды во время работы в IntelliJ IDEA он с удивлением понял, что, занимаясь рефакторингом, он практически не пишет код, а лишь манипулирует абстрактным представлением программы. Разумеется, после этого IDE отображала эти изменения в тексте программы, но суть от этого не менялась – Мэтт менял не код, а абстрактное представление. Если вам тоже доводилось ощущать нечто подобное во время работы с современной средой разработки, то вы уже представляете, что такое работа с языковым инструментарием.

Рисунок. 2: Манипулирование различными представлениями программы с помощью языкового инструментария.

На рисунке 2 изображен тот же процесс, но уже в языковом инструментарии. Самое главное отличие – «исходным» становится уже не редактируемый текстовый файл. Здесь вы изменяете само абстрактное представление программы. Чтобы у вас была возможность редактировать абстрактное представление, оно должно подаваться в каком-нибудь редактируемом виде. И теперь уже редактируемое представление программы будет недолговечным (оно нужно только чтобы облегчить работу). А настоящим «исходником» программы будет постоянно существующее абстрактное ее представление.

Тот факт, что редактируемое представление программы является всего лишь временной проекцией, подводит к нескольким интересным выводам. И возможно, самым важным из них будет то, что редактируемое представление совсем не обязательно должно быть полным и законченным. Если отдельные его составляющие не важны для текущей задачи, то их вообще можно не показывать. Более того, редактируемых представлений может быть несколько, и каждое из них призвано иллюстрировать некоторые аспекты абстрактного представления. А поскольку абстрактная проекция находится внутри языкового инструментария, то редактируемое представление будет гораздо активнее, чем обычный текстовый файл. Таким образом, редактор абстрактной проекции окажется тесно связан с языком программирования, а следовательно во время работы с редактируемыми представлениями вы будете активно думать о том, как преобразовывает их редактор. Возможно, у вас будут возникать гораздо более интересные идеи, чем те, которые вы можете придумать при работе с таким пассивным видом редактируемого материала, как текст.

Языковой инструментарий отделяет представление, в котором хранится код, от его редактируемой ипостаси. Теперь хранение кода осуществляется через сериализацию абстрактной проекции программы. Обычно это делается с помощью XML, однако в данном случае этот XML уже не предназначен для редактирования человеком. Выбор XML для хранения программного кода облегчает взаимодействие приложения с другими системами. Хотя, конечно, достичь такого взаимодействия будет очень и очень непросто.

С генерацией кода происходит похожая ситуация, хотя обычно кодогенераторы рассматривают в качестве исполнимого кода обычные исходники. Исходные файлы, созданные обычными кодогенераторами, по сути не являются исходными, потому что они не предназначены для прямого редактирования. По мере развития языкового инструментария мы будем все больше полагаться на такую генерацию не редактируемых структур, например в байт-код.

Абстрактное представление, с которым мы работаем в языковом инструментарии, должно быть терпимо к ошибкам и всевозможным неточностям. Возможно, это не самая очевидная его характеристика, но тем не менее, одна из наиважнейших. Принято считать, что абстрактное представление программы должно быть абсолютно правильным, что в нее нельзя вносить неточную информацию. Так вот, это предположение приводит к отсутствию юзабилити таких приложений. Те среды разработки, которые пошли по стопам IntelliJ IDEA, прекрасно уяснили это, и теперь весьма элегантно обращаются с ошибками в коде. Например, вы сможете заниматься рефакторингом в приложениях, которые компилируются с ошибками (это – признак высокой юзабилити).

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

Определяем новый DSL

Наметив общие цели и установки языкового инструментария, обратимся теперь к процессу создания новых DSL. Он состоит из трех шагов:

  • Определить абстрактный синтаксис, который станет схемой абстрактного представления программы.
  • Выбрать редактор , который позволил бы людям манипулировать абстрактным представлением с помощью некой проекции.
  • Создать генератор , который будет транслировать абстрактное представление в исполнимое. В действительности генератор определяет семантику DSL.


Разумеется, это только основные пункты, кроме того, в них возможны некоторые вариации. Как я уже говорил ранее, при работе с DSL вполне можно воспользоваться несколькими редакторами и генераторами. У разных людей могут быть разные предпочтения при выборе редактора. Например, в редакторе Intentional можно переключаться между несколькими проекциями одной и той же модели и видеть иерархическую структуру данных как LISP-образные списки, как вложенные блоки или же в виде дерева.

Разные редакторы понадобятся по нескольким причинам. Какие-то из них могут оказаться удобнее для работы с определенными фреймворками, поддерживающими схожую функциональность. Хороший пример такого случая – многочисленные диалекты SQL. Кроме того, различные компромиссы в разработке могут возникать из-за различия в характеристиках производительности или библиотеках. И наконец, может понадобиться генерировать код на разных языках – то есть чтобы ваш DSL мог генерировать код и на Java, и на C#.

Кроме того, может оказаться полезной возможность определять трансляторы для хранения данных. Вполне вероятно, что языковой инструментарий будет по умолчанию создавать какую-то схему хранения данных, которая автоматически осуществит сериализацию абстрактной модели. Тем не менее, может понадобиться несколько альтернативных представлений хранения данных. Например, для того, чтобы поддержать возможность взаимодействия между различными системами, или для передачи данных из одного инструмента в другой. В отличие от генераторов кода, в данном случае общение с таким представлением будет двусторонним.

Дополнительный вид генератора может создавать документацию, которую сможет читать человек (эквивалент javadoc). Разумеется, все взаимодействие с языковым инструментарием должно осуществляться с помощью редактора, однако это не отменяет необходимости создавать документацию – как бумажную, так и электронную.

Более подробно об этом написано в статье A Language Workbench in Action – MPS (http://martinfowler.com//articles/mpsAgree.html). В ней показан пример создания новыого DSL с помощью Meta-Programming System (MPS) компании JetBrains. Это реальный пример работающего языкового инструментария.

Создаем новый языковой инструментарий

На сегодняшний день не существует единого определения того, что же представляет собой языковой инструментарий. Неудивительно, что мне пришлось изобретать столько новых терминов для этой статьи! Меня поражает еще и то, что мне приходится определять здесь и основные характерики языкового инструментария. В противном случае, мы столкнемся с такой же жуткой неразберихой, которая окружает большинство тем, касающихся программирования (например, понятия «компонентов» (components), сервисно-ориентированной архитектуры (Service Oriented Architecture) и т.д.).

Впрочем, теперь, после того, как мы довольно подробно рассмотрели многие вопросы, связанные с языкоориентированным программированием, определить базовые свойства языкового инструментария совсем не трудно:

  • Пользователи языкового инструментария могут легко создавать новые языки, которые при этом будут взаимно интегрированы.
  • Главным и основным источником информации является постоянно существующая абстрактная модель приложения.
  • Новый DSL создается в трех основных составляющих: схема, редактор (редакторы) и генератор (генераторы).
  • Пользователи нового языка манипулируют им с помощью проекционного редактора.
  • Языковой инструментарий может работать, даже если абстрактная модель содержит неполную или противоречивую информацию.

Как языковые инструментарии меняют соотношение «за» и «против» в языкоориентированном программировании

Некоторое время назад я говорил о плюсах и минусах языкоориентированного программирования. Языковой инструментарий оказывает непосредственное влияние на соотношение сил в этом вопросе и вносит несколько новых аргументов.

Самый большой плюс языкового инструментария – легкость создания внешних DSL. Теперь не нужно писать парсер. Достаточно определить абстрактный синтаксис – но это обычный этап моделирования данных. К вашим услугам будет мощная среда разработки (IDE). Правда, потребуется некоторое время, чтобы внести нужные установки в ее редактор. Вам все также придется создавать генератор, и мне кажется, что языковый инструментарий не сделает эту задачу проще. Но с другой стороны, написать генератор для хорошего и несложного DSL – задача почти что пустяковая.

Еще одно большое достоинство языкового инструментария состоит в том, что он дает интеграцию на уровне символов. Это же просто здорово – взять похожий на Excel язык формул и просто вставить его в тот язык, который вы сами создали. То же самое можно сказать и о возможности менять символы в одном языке, с тем, чтобы эти изменения прошли через всю систему (языковому инструментарию это вполне по силам, хотя я не уверен, что они уже умеют это на сегодняшний день).

Еще одним важным моментом в использовании языкового инструментария является рефакторинг. Когда я говорю о языковом инструментарии, вы можете представить себе что-то вроде: «Сначала создай DSL, а потом пиши на нем то, что его использует». Если вы знакомы с другими моими работами, то это должно вас насторожить. Да, я большой поклонник эволюционного проектирования, что в данном контексте означает – у вас должна быть возможность одновременно менять и сам язык, и написанный на нем код. Это весьма сложная проблема, и разработчики Intentional с самого начала решили учесть ее. Пока еще нельзя сказать, появится ли в будущих, готовых, версиях языкового инструментария такая возможность, но если нет, то это будет очень большим препятствием к его использованию.

Могу назвать еще одну большую проблему. Это зависимость от поставщика языкового инструментария. Пока не существует никаких стандартов для определения трио «схема, редактор и генератор». Создав язык в каком-либо языковом инструментарии, вы тут же попадаете в зависимость от него. Раз нет никаких стандартных способов обмена данными между разными языковыми инструментария, значит при переходе на другой языковой инструментарий придется создавать заново и схему, и редактор, и генератор. Может быть, с течением времени возникнет некий специальный вид хранения данных – специально для таких случаев. Однако если этого не случится, то риск зависимости от поставщика инструментария будет весьма большим. (Архитектура MDA дает некоторый ответ на эту проблему, но на мой взгляд, он по меньшей мере неполон.)

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

Вопрос выбора инструментария остро встает вследствие ухода от использования текста как носителя исходного кода. Вам придется заново решать различные проблемы, которые уже успешно решались для текстовых файлов. Я имею в виду управление версиями для абстрактных моделей. Мы уже научились делать хорошие инструменты такого рода для текстовых файлов – с возможностью посмотреть разницу в файлах, а затем внести ее в результирующий вариант. Языковой инструментарий должен предлагать такую же функциональность для абстрактных представлений, иначе с ним будет сложно работать. Теоретически, эта проблема, конечно же, решается. Более того, ее решение позволит видеть семантические отличия абстрактных моделей (когда можно явно увидеть, что две модели отличаются переименованным символом, а не догадываться об этом по изменениям в тексте.) Компания Intentional придумала для этого неплохой вариант решения, однако подождем их хвалить, пока не опробуем его в деле.

Но вернемся к положительным сторонам перехода на языковые инструментарии. Прежде всего, та комбинация нового специфического языка программирования и редактора, в котором это можно осуществлять, открывает путь к программированию для непрофессионалов. Более того, интеграция на уровне символов сводит на нет проблему разницы кода, написанного профессионалом и непрофессионалом. Может быть, мощные современные редакторы дадут возможность преодолеть проблему языка COBOL, так как предоставят непрограммистам среду, специально настроенную на нужды их работы.

Да, пожалуй, возможность привлечения к программированию знатоков предметной области – самая захватывающая из всех, связанных с языкоориентированным программированием. Похоже, что когда мы снова и снова говорим о том, что какое-то приложение серьезно увеличивает продуктивность работы программиста, мы пытаемся оптимизировать пустой цикл. В абсолютном большинстве проектов, с которыми мне доводилось сталкиваться, самой большой проблемой было общение между программистами и людьми, для которых они писали программное приложение. Если решить эту проблему, можно достичь больших успехов даже без самых лучших технологий. А если эта проблема стоит остро, то никакой Smalltalk вас не спасет.

Большинство энтузиастов языкоориентированного программирования говорят о привлечении к программированию знатоков предметной области. Я даже слышал о том, что секретарши будут программировать на внутренних DSL, созданных на Lisp. К сожалению, по опыту известно, что подобные утверждения еще ни к чему не приводили. Может быть хоть в этот раз мы сможем наконец сделать это – ведь теперь появилась возможность объединить достоинства специфического для данной предметной области языка и мощного инструментария, в котором этот язык можно редактировать. Если это получится, то выгода от использования языкоориентированного программирования будет неизмерима. Я был просто поражен, когда узнал, насколько важным считают вовлечение в процесс непрограммистов Чарльз Симони и разработчики из Intentional Software, и как это влияет на все принимаемые ими решения.

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

Изменения в нашей концепции DSL

В этой статье я приводил довольно малоинтересные варианты DSL. Их было легче использовать, потому что их просто писать и удобно приводить в пример. Однако даже более сложный DSL для составления соглашений весьма условен – хорошо видно, как его можно представить в виде обычного текстового варианта языка. Многие рассчитывают на появление графических DSL, однако даже это не может продемонстрировать все их возможности. Самую большую опасность представляет сам термин «язык», который может скрыть от людей те возможности, которые скрывает в себе языковой инструментарий.

ПРИМЕЧАНИЕ

См. статью «A Language Workbench in Action – MPS», http://www.martinfowler.com/articles/mpsAgree.html#AgreementDsl

Когда мы с коллегами обсуждали конференцию OOPSLA 2004, больше всего разговоров шло о выступлении Джонатана Эдвардса, который рассказывал о «Примеро-центрическом программировании» (Example Centric Programming). Главным в этой методологии является редактор, который отображает не только программный код, но и результаты выполнения фрагментов этого кода. Суть метода заключается в том, что нам легче думать о конкретных примерах даже тогда, когда мы манипулируем абстрактной моделью. Такая же склонность к примерам привлекает многих и к «Разработке, основанной на тестах» (Test Driven Development). Я думаю, это можно назвать Specification by Example (http://martinfowler.com/bliki/SpecificationByExample.html).

На основе своих идей Эдвардс создал собственное приложение под названием Subtext (http://subtextual.org/). Во многом оно напоминает языковой инструментарий (например, в отказе от текстовых исходных файлов). Subtext менее интересен с точки зрения создания новых языков, однако он дает понимание того, как может меняться мышление программиста с развитием языковых инструментариев, мышление, основанное на том, что язык и инструмент тесно переплетены между собой.

И это именно та причина, которая сможет помочь языкоориентированному программированию преодолеть опасности, погубившие планы разработчиков COBOL. Как я уже говорил выше, мы постоянно пытаемся изобрести технологии, которые позволили бы пользователям выступить в роли программистов. И нам это постоянно не удается. А теперь давайте обратимся к одной технологии, в которой нам удалось достичь этой цели – к электронным таблицам (spreadsheets).

Большинство программистов никогда не назовут работу с электронными таблицами программированием. И тем не менее, пользователи – непрофессиональные программисты – умудряются создать с их помощью весьма сложные системы. Электронные таблицы – это замечательная среда программирования, которая уже показала, какие свойства должны быть у инструмента, предназначенного для непрофессионального программирования:

  • Незамедлительная обратная связь с пользователем, включая немедленное отображение результатов вычисления.
  • Глубокая взаимосвязь инструмента и языка программирования.
  • Отсутствие текстовых исходных файлов.
  • Отсутствие постоянного отображения всей имеющейся информации – формула выводится только при редактировании ячейки, в которой она содержится. Во всех остальных случаях программа показывает ее значение.

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

Итак, когда мы говорим о работе с DSL в языковом инструментарии, мы должны думать не столько о тех примерах, которые я здесь приводил, и не о графических языках, которые так любят те, кто занимается моделированием приложений. Лучше представить себе, что языковой инструментарий станет следующим поколением электронных таблиц.

Заключение

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

С моей точки зрения, языковой инструментарий дает два основных преимущества. Во-первых, это большая продуктивность работы, возникающая за счет использования лучших инструментов. Во-вторых, это большая продуктивность работы, которую дает более тесное сотрудничество между программистами и людьми, знающими предметную область. Фактически, это возможность для непрограммистов непосредственно участвовать в разработке приложения. Конечно, понять, насколько реализовались эти преимущества, можно только по прошествии некоторого времени. Сам я думаю, что первое преимущество никуда не денется, программистам будет действительно удобнее работать с языковым инструментарием. Однако оно не окажет глобального влияния на всю программную отрасль. А вот если языковые инструментарии изменят отношения между программистами и экспертами предметной области – вот это уже будет иметь просто ошеломительный эффект. Но для этого языкоориентированному программированию надо учесть и преодолеть уроки COBOL.

И пожалуй, самое интересное – если мы будем использовать языковые инструментарии, то едва ли сможем себе представить, как будут выглядеть DSL будущего. До сих пор мое представление ограничивалось тем, как могут выглядеть текстовые или графические языки. Но при тесном взаимодействии редактора и схемы перед нами откроются такие возможности, какие и в голову не приходят сейчас программистам, пишущим внешние DSL. И если языковой инструментарий оправдает наши надежды, то уже через десять лет мы будем смеяться над тем, как мы представляли себе DSL.

Как я уже говорил, сейчас языковой инструментарий находится на самой ранней стадии развития. И только через несколько лет мы сможем по-настоящему опробовать, на что он способен. Не буду ничего говорить о том, насколько языковой инструментарий изменит всю парадигму программирования, как надеются его фанаты. Из меня вообще неважный предсказатель. Скажу только, что это – одна из самых интересных идей на горизонте современного программирования. И если разработчики языкового инструментария осознают весь потенциал своих приложений, это будет иметь огромный резонанс во всем, что связано с нашей профессией. И даже если этого не случится, я думаю, что их разработки подарят нам немало весьма и весьма интересных идей.

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

Дополнительные материалы:

Copyright © 1994-2020 ООО «К-Пресс»

Ruby on Rails за 4 месяца

Изучение ruby для новичков. С полного нуля до собственных готовых сайтов на ruby on rails. Пошаговое изучение с самых основ.

пятница, 26 октября 2012 г.

DSL в ruby

Сегодня рассматриваем следующий скринкаст DSL — Domain Specific Languages
Ну прежде чем рассматривать этот скринкаст разберем и выполним пример из статьи DSL и динамические вкусности Ruby
Что такое DSL? Это как бы специальный язык (формат записи) который разбирается интерпретатором и преобразуется в обычный код.
Чтобы освежить в памяти работу с блоками хорошо прочитать эту статью где все доступно описано.
Итак стоит описать всю ту путаницу что происходит в этом скринкасте.
1. Вызывается метод config класса StoreApplication.
2. При вызове метода ему передается блок
3. При передаче блока задается переменная app в которую поместится значение заданное в yield при обработке этого блока в методе config
4. Таким образом начинает выполнятся метод config класса StoreApplication.
5. Т.к. у нас в начале метода указано
То первым делом управление передается блоку и в переменную app присваивается self (т.е. сам класс)
6. Далее происходит выполнение кода в блоке
Как будто код выполняется внутри класса, происходит задание этих значений. Т.к. мы указали внутри класса attr_accessor :name, :environment
7. Далее происходит выполнение
Вызывается метод admin внутри класса StoreApplication
8. Внутри этого метода происходит создание класса Admin и ему передается 9. Т.е. управление переходит на метод new класса Admin
10. В методе new идет вызов yield т.е. управление передается блоку
и в переменную |admin| передается класс Admin
11. Происходит выполнение кода в блоке
В данном блоке происходит задание значений email, login, send_info_emails_on внутри класса Admin

Репозитории Ruby DSL (Domain Specific Language), примеры

Я ищу отличные примеры Ruby DSL (Domain-Specific Languages). Какие репозитории, проекты, о которых вы знаете, которые стоит прочитать? Почему это (или: они) отличные примеры?

Меня особенно интересуют более сложные примеры, которые хорошо продуманы и разработаны.

6 ответов

Rake и Rack являются хорошими примерами DSL. Если вы хотите еще несколько примеров, проверьте их:

  • Sinatra — это очень популярный DSL для создания веб-приложений, и он с открытым исходным кодом на GitHub.
  • Twibot — это новый DSL, созданный по мотивам Sinatra, который позволяет создавать боты Twitter, которые автоматически отвечают на сообщения и ответы.

Если вы хотите начать создавать свои собственные, вот отличное руководство под названием Создание DSL в Ruby.

В области развития, управляемого поведением, вы можете проверить:

  • Огурец — Опишите BDD, используя сценарии
  • RSpec — Заменить тестовый код с указанием поведения.


Хотя я должен признать, что код RSpec иногда заставляет меня чесать голову, потому что я все еще новичок.

Вы проверили драгоценность Docile, это может быть самый простой и чистый способ удовлетворить ваши потребности?

Другой пример, конечно, Rake, система сборки Ruby. Что делает DSL «хорошим» на мой взгляд:

  1. Нотация соответствует значению, т.е. если вы читаете предложение (утверждение) в DSL, у вас есть четкое, однозначное представление о том, что оно делает.
  2. Специфичный для домена, т. Е. DSL не решает все проблемы во вселенной, а скорее фокусируется на одном маленьком домене (например, создание программного обеспечения, запрос данных или создание пользовательского интерфейса)
  3. Высокий уровень абстракции. DSL использует концепции высокого уровня, которые может использовать программист, и переводит их в низкоуровневую реализацию (внутренне). В случае Rake основной концепцией, на которой основан язык, являются задачи и зависимости между ними.

Репозитории Ruby DSL (Domain Specific Language), примеры

Я ищу отличные примеры Ruby DSL (Domain Specific Languages). Какие репозитории, проекты, которые вы знаете, стоит прочитать? Почему это (или: они) отличные примеры?

Мне особенно интересны более сложные примеры, которые хорошо продуманны и разработаны.

Rake and Rack — неплохие примеры DSL. Если вам нужны еще несколько примеров, проверьте их:

  • Sinatra — очень популярный DSL для создания веб-приложений, и он открывает исходный код на GitHub.
  • Twibot — это новый DSL, вдохновленный Sinatra, который позволяет создавать ботов Twitter, которые автоматически отвечают на сообщения и ответы.

Если вы хотите начать свой собственный, вот отличный учебник под названием Построение DSL в Ruby.

В области Behavior-Driven Development вы можете проверить:

  • Cucumber — Опишите BDD, используя сценарии
  • RSpec — Замените тестовый код указанием поведения.

Хотя я должен признать, что код RSpec оставляет меня иногда царапать мою голову, потому что я все еще очень начинающий.

Вы проверили Docile gem, это может быть самый простой и самый чистый способ удовлетворить ваши потребности?

Другим примером, конечно, является Rake, система сборки Ruby. Что делает DSL «хорошим» на мой взгляд:

  • Обозначение соответствует значению, т.е. если вы читаете предложение (утверждение) в DSL, у вас есть четкое, однозначное представление о том, что он делает.
  • Домен, т.е. DSL не решает каждую проблему во Вселенной, а фокусируется на одном небольшом домене (таком как создание программного обеспечения, запрос данных или создание пользовательских интерфейсов).
  • Высокий уровень абстракции. DSL использует концепции высокого уровня, которые может использовать программист, и переводит их на более низкую реализацию (внутренне). В случае с Rake основной концепцией, на которой основан язык, являются задачи и зависимости между ними.

Язык программирования Ruby

Введение

Ruby — один из самых молодых языков программирования. Его создатель Юкихиро Мацумото (Yukihiro Matsumoto, также известный под псевдонимом Matz), профессиональный японский программист, рассказывает: > Название языка происходит от имени драгоценного камня рубина (по аналогии с другим широко распространенным языком программирования Perl: pearl — жемчуг).

Вот как характеризует Ruby его автор: > [1].

В Японии Ruby стал популярным с момента появления первой общедоступной версии в 1995 году, однако наличие документации только на японском языке сдерживало его дальнейшее распространение. Лишь в 1997 году появилось описание Ruby на английском языке, а в 1998 году открылся форум ruby-talk. С этого момента Ruby начал свое шествие по всему миру. За последний год появились три англоязычные книги, посвященные ему (см. [2-5]), хотя на русский язык, к сожалению, ни одна из них еще не переведена. Сейчас Ruby входит в большинство дистрибутивов ОС Linux, доступен он и пользователям других операционных систем.

Заметим, что данная статья не является справочным руководством по языку. С кратким введением можно познакомиться в электронном учебнике [6], а более полная информация может быть найдена в [7] и [8], а также на уже упомянутых выше сайтах сети интернет. Основная цель статьи — дать людям, уже знакомым с различными языками программирования, общее представление об особенностях Ruby, показать его мощь и красоту, объяснить, что он одинаково хорош и как первый язык программирования, и как средство для работы профессионального программиста и системного администратора.

Программисты говорят о Ruby

В этом разделе мы изложим собранные из различных источников высказывания о языке Ruby, отложив на некоторое время рассмотрение примеров, иллюстрирующих их. Большая часть вопросов, которые будут затронуты, предполагает наличие определенных знаний у читателя в области теории языков и объектно-ориентированного программирования. Легче всего понять Ruby тем, кто знает Smalltalk, Lisp, C, C++ и Perl. > набор из Perl, Java и C++ тоже является весьма хорошей стартовой позицией для изучения Ruby.

Тем же, кто не может похвастаться подобными знаниями, весьма приятно будет узнать, что

Во введении мы уже цитировали Юкихиро Мацумото — создателя Ruby. Вот что еще он сам говорит об этом языке. Итак, Ruby

* имеет простой синтаксис;

* поддерживает обработку исключений;

* позволяет переопределять операторы;

* является чисто объектно-ориентированным языком (complete, full, pure object oriented language), в котором, в отличие от Java или Perl, все — объекты;

* позволяет работать с целыми числами произвольной величины;

* не требует объявления переменных;

* использует префиксы (@, $, @@) для задания области видимости (scope) переменных;

* поддерживает многопоточное программирование.

Одной из первых работ, привлекших внимание к Ruby, была уже несколько устаревшая статья Хала Фултона (Hal Fulton) >. Вот некоторые из них.

Ruby является динамическим языком. В отличие от статических языков, подобных C++ или Java, методы и переменные в Ruby могут быть добавлены или переопределены во время выполнения программы. Это позволяет, например, обойтись без директив условной компиляции #ifdef, необходимых для языка C. Здесь проявляется сходство Ruby с такими языками, как Lisp и Smalltalk.

Ruby — интерпретируемый язык. Так как это свойство отрицательно сказывается на производительности, то хочется дать некоторые комментарии. Во-первых (и это самое главное), быстрый цикл разработки является важнейшим достоинством, которое > некоторые недостатки. Во-вторых, хотя Ruby и не является катастрофически медленным, в тех случаях, когда скорость абсолютно необходима, можно написать часть кода на языке C. Наконец, в-третьих, никто не сказал, что когда-нибудь не будет написан компилятор с языка Ruby.

В Ruby имеется возможность работы с регулярными выражениями. Когда-то они использовались только в некоторых утилитах UNIX-систем (grep, sed, vi). Затем, благодаря языку Perl, значительное число людей признало их мощь при обработке текстов. Ruby дает возможность использовать эту силу с еще большей легкостью.

Помните известную фразу Исаака Ньютона >? Ruby, безусловно, >, среди которых Smalltalk, CLU, Lisp, C, C++, Perl, Kornshell и другие языки. В Ruby собрано все лучшее, что накоплено человечеством в области языков программирования. При этом соблюдены следующие три принципа: не изобретать колесо, не чинить не сломанное, использовать имеющиеся у программистов знания и опыт. В Ruby пригодятся и знания о файлах и потоках из ОС UNIX, и спецификации функции printf из стандартной библиотеки ввода/вывода языка C, и умение работать с регулярными выражениями в Perl.

Ruby — язык написания сценариев (scripting language). Не следует считать, что это характеризует язык, как > или недостаточно мощный. Подобный язык должен быть интерпретируемым и способным заменить командные файлы, взаимодействующие с операционной системой и управляющие поведением других программ.

Ruby — универсальный и гибкий язык. С его помощью можно одинаково изящно реализовать как то, что традиционно делается с помощью интерпретатора Kornshell, так и то, что пишется обычно на C.

По простоте и богатству возможностей работы со строками и массивами языку Ruby нет конкурентов. Массивы являются динамическими, с ними можно оперировать как с множествами, стеками и очередями. Есть и ассоциативные массивы, работа с которыми также чрезвычайно проста и удобна.

Для языка Ruby уже написано большое число библиотек. Потоки, сокеты, объекты, сохраняемые между запусками программ, CGI-программы, базы данных, GUI — все это можно использовать, программируя на Ruby.

Дэйв Томас (Dave Thomas) и Энди Хант (Andy Hunt), соавторы первой книги по Ruby, вышедшей на английском языке, в интервью журналу «Dr. Dobb’s Journal» (январь 2001) говорят: > Фактически это означает, что Ruby — естественный и практически неизбежный результат эволюции современных языков программирования. Авторы данной статьи согласны с такой точкой зрения.

Хочется подчеркнуть, что Ruby не является панацеей для решения всех проблем программистов. Не следует отказываться от использования языков Java и C++ там, где их применение оправдано. С другой стороны, не разумно недооценивать возможности практического применения Ruby в реальных проектах.

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

Начнем с языка Java:

* Ruby — интерпретируемый язык;
* в Ruby все является объектом (в Java есть типы int и Integer, что создает определенные неудобства);
* переменные в Ruby не являются статически типизированными и не требуют объявления;
* модули (modules) в Ruby позволяют с помощью > (mixins) конструировать подобие интерфейсов (interfaces) языка Java, допуская при этом в них реализацию методов.

Теперь сравним с языком Perl:

* Ruby значительно легче освоить, на нем легче программировать, а написанные программы проще сопровождать;
* в Ruby префиксы (@, $, @@) описывают область видимости (scope), а не тип переменной;
* Ruby позаимствовал из языка Perl регулярные выражения, переменную $_ и многое другое.

Завершим сопоставление Ruby с другими языками рассмотрением одного из ближайших > — языка Python. Это сравнение особенно интересно потому, что именно Python рассматривается сейчас многими, как хороший кандидат на роль первого языка программирования. Итак, сравним Ruby с языком Python:

* управляющие конструкции и методы в языке Ruby завершаются ключевым словом end, в то время как Python использует так называемый > синтаксис, когда признаком завершения является изменение количества лидирующих пробелов в очередной строке программы;
* вместо self в Ruby для обозначения переменных экземпляра используется префикс @;
* в Ruby, в отличие от языка Python, понятия типа и класса являются синонимами;
* Python не поддерживает наследования и не позволяет добавлять методы к существующим типам;
* используемый в Ruby алгоритм сборки мусора позволяет проще писать реализации методов на языке C;
* расширения для Ruby, написанные на C/C++ позволяют определять новые классы;
* зачастую Ruby быстрее, чем Python.

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

Программисты пишут на Ruby

Начнем с примеров, показывающих, что знание библиотек ввода/вывода языков C и C++ пригодится и в Ruby.

Специальная глобальная переменная $_ всегда содержит результат последней операции чтения. Она же используется как аргумент по умолчанию во многих конструкциях. Следующая программа, например, печатает все строки из входного потока, которые содержат слово Ruby.

Ruby-стиль, однако, рекомендует использовать итератор each:

ARGF в Ruby — это объект, который представляет собой конкатенацию содержимого всех файлов, имена которых заданы в командной строке, либо просто стандартный поток ввода (в случае отсутствия аргументов).

Вот как выглядит на Ruby программа вычисления факториала числа, указываемого в качестве аргумента командной строки:

Для вычисления с помощью этой программы, размещенной в файле fact.rb, значения 100! достаточно выполнить команду ruby fact.rb 100.

Три программы, рекурсивно вычисляющие 30-е число Фибоначчи, приведенные ниже, позволяют сравнить производительность интерпретаторов Ruby, Python и Perl.

Еще одна классическая задача — определение с помощью решета Эратосфена списка всех простых чисел, не превосходящих заданного (100 по умолчанию).

В качестве следующего примера рассмотрим решение на языке Ruby задачи, которая часто предлагается студентам первого курса, изучающим языки C/C++: для заданного текстового файла определить число вхождений в него каждого из встречающихся в нем слов. Использование ассоциативных массивов и ряда стандартных методов работы с файлами и строками позволяет написать чрезвычайно краткую и ясную программу.

Приведем два простых примера использования стандартной библиотеки классов. В результате выполнения первой программы будет найдено, что 7/8+1/8=1, а 7/8*1/8=7/64; вторая из них вычислит (1 + i)64.

Без подробных объяснений приведем две эквивалентные программы, иллюстрирующие переопределение оператора [] для класса SongList. Ассоциативный массив (associative array, hash или dictionary) допускает индексирование произвольными объектами, а не только целыми числами. В данном случае оператор [] позволяет находить нужную песню не только по номеру, но и по ее названию.

Так как Ruby унаследовал лучшие особенности многих языков, то для выполнения достаточно стандартных действий обычно имеется несколько разных возможностей. Вот 13 (!) различных способов напечатать числа от 0 до 9:

В заключение вопрос для тех, кто не знает языка Ruby: что напечатает следующая программа?

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

Ruby и новички

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

Не менее важны мультиплатформенность Ruby и его принадлежность к миру свободно распространяемого ПО. Еще один весомый аргумент в его пользу — возможность практического использования языка в самых разных областях, что не позволит впоследствии профессионалу, который вырастет из новичка, пожалеть о напрасно потраченном времени.

Можно заметить, что значительному числу перечисленных выше требований удовлетворяют и Python, и Java, и C++ и даже (в меньшей степени) Delphi. Последний из них, правда, является коммерческим продуктом, что автоматически должно было бы повлечь исключение его из рассмотрения. Однако в России даже среди организаторов олимпиад по программированию для школьников находятся люди, утверждающие, что стоимость дистрибутива Delphi составляет около 70 рублей!

Язык C++ практически никто и никогда не рекомендовал в качестве первого языка — слишком он сложен. Java, наоборот, используется как первый язык, достаточно часто. Python сейчас рассматривается многими как неплохой кандидат на эту роль.

Попробуем сначала сравнить языки Ruby и Java, посмотрев на них глазами человека, только начинающего знакомиться с миром программирования. Для такого человека чрезвычайно важна ясность и краткость программ, отсутствие в них странных и непонятных > слов. А именно из них и состоит в основном Java-вариант традиционной первой программы!

Аналогичную программу на Ruby долго объяснять не придется:

puts «Здравствуй, мир!»

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

В отличие от языка Java в Ruby нет проблемы > чисел. Приведенные выше программы вычисления факториала числа или чисел Фибоначчи, будучи написанными на Ruby, всегда дают верный результат. В случае же языка Java ограничения на диапазон представимых целых чисел приводят к нелепым (с точки зрения ученика школы или студента первого курса) равенствам типа

Неприятная неожиданность для человека, только приступающего к изучению программирования!

Еще одним важным фактором является простота методов, используемых в Ruby для ввода данных с клавиатуры или из файла. В случае языка Java сложность этих операций приходится тщательно маскировать, ограждая новичка от ненужных ему проблем.

Компактность дистрибутива Ruby и простота его установки в любой операционной системе позволяют школьнику или студенту без проблем работать на домашнем компьютере. Опять сравнение не в пользу Java!

Язык Python по многим параметрам похож на Ruby. Из приведенных выше различий этих двух языков для рассматриваемой нами ситуации начального обучения программированию особенно значимым оказывается > синтаксис, используемый в языке Python. Эта особенность не является изначально понятной и на первых порах мешает. Вообще, основным аргументом в пользу Ruby в качестве первого языка программирования является именно его интуитивная ясность и предсказуемость, а не многие другие его преимущества.

Ruby и профессионалы

Чем же хорош Ruby для профессионального программиста и системного администратора? Многое уже было сказано выше, но вот еще несколько интересных примеров и кратких комментариев.

По умолчанию строка, заключенная в обратные апострофы, заменяется результатом выполнения команды, в ней заданной. Можно, однако, и переопределить это поведение:

CGI-скрипты, формы, работа с > (cookies) — это только начало. Программы на Ruby могут быть внедрены прямо в HTML, что эквивалентно использованию таких средств, как ASP, JSP или PHP. При этом сохраняется вся мощь Ruby, а для повышения производительности можно использовать специальный модуль для Apache.

Ниже приведена программа, иллюстрирующая Tk расширение языка Ruby. Левая кнопка мыши позволяет рисовать прямые линии, а правая — создавать Postscript-файл с построенным изображением.

Отметим, наконец, что конфигурация и установка дистрибутива Ruby осуществляется с помощью скриптов, написанных на нем самом.

Заключение

Выпускники МГИУ специальности 2204 этого и предыдущего годов (многие из которых являются сейчас аспирантами) могут обнаружить, что язык Ruby во многом похож на известный им по занятиям на первом курсе язык C-Talk. Этот язык, который разрабатывался в нашем университете группой программистов под руководством Игоря Вячеславовича Абрамова (см. [9-11]), был основан в значительной мере на тех же самых идеях, которые взяты в качестве основополагающих автором Ruby.

Осенью 1997 года студенты-программисты в нашем университете начали изучать язык Java. МГИУ был тогда единственным вузом в Москве, в котором первым языком программирования стал этот ныне общепризнанный язык.

Сейчас к связке языков Java и C++, двух основных языков современных профессиональных программистов, мы добавляем Ruby, с которого и начнется знакомство в новом учебном году первокурсников с миром программирования. Очень хочется верить в то, что и на этот раз наш выбор не окажется ошибочным.

Цукерберг рекомендует:  Приложение - Нужна помощь новичку в C#
Понравилась статья? Поделиться с друзьями:
Все языки программирования для начинающих