Предметно-ориентированные языки (DSL) в прикладных приложениях используются намного чаще, чем может показаться на первый взгляд. Пожалуй, самый распространенный в повседневной практике случай использования DSL — это разнообразные конфигурационные файлы.
Для описания каких-то сложных конфигураций довольно часто используют XML. Сегодня он де-факто стал чем-то вроде универсального DSL, благодаря обилию готовых средств синтаксического анализа, лексического разбора и всего остального, что необходимо для работы. Но универсальные плюсы влекут за собой и минусы. Давайте посмотрим.
Скажем, поставлена задача разработать программу для светофора. Светофоры в принципе все одинаковые, но вести они себя должны по-разному. Настраивая каждый отдельный светофор, мы можем либо хранить логику его работы в коде, перекомпилируя программу для каждого отдельного светофора (что неприемлемо!), либо вынести эту логику во внешний конфигурационный файл и поручить настройку отдельных светофоров сотрудникам ГИБДД (то есть в общем случае это будут эксперты предметной области, а не программисты).
Представим, как бы мог выглядеть этот конфигурационный файл в формате XML. Пусть конкретный светофор ведет себя следующим образом. 10 секунд горит красный, затем 3 секунды мигает желтый и, наконец, 7 секунд горит зеленый. Проявив немного фантазии, можно представить себе вот такой XML-файл:
<trafficlight> <red>10</red> <yellow blink="yes">3</yellow> <green>7</green> </trafficlight>
Этот файл мал и прост, но мы представим, что конфигурация очень сложная и на бумаге занимает много страниц — в него можно добавить зависимость режима работы от времени суток, синхронизацию с другими светофорами и многое другое, чего душа пожелает. Для нас — программистов — работа с таким форматом файла потребует минимум усилий. Идиллия? Не совсем.
Каковы минусы? В этой конфигурации большое количество визуального мусора и ненужных деталей. Этот «код» плохо читаем. Читаемость очень важна при общении с экспертами предметной области и в их дальнейшей самостоятельной работе. Но мы обычно ленимся предложить что-то удобнее XML, а заказчики зачастую даже не знают, что можно делать иначе.
Но иначе можно.
Что, если записать настройки так?
red 10 yellow 3 blink green 7
Уже чуть больше ясности. Или даже так, добавим гибкости, чтобы в будущем иметь возможность менять начальное состояние светофора и задавать более сложные режимы работы:
init red off yellow off green off endinit loop red on wait 10 red off yellow on wait 1 yellow off wait 1 yellow on wait 1 yellow off green on wait 7 green off endloop
Мне нравится эта программа. Давайте попробуем её выполнить. Так как у меня дома нет настоящего светофора (а жаль), то программировать придется его простую программную модель.
Этот синтаксис очень прост. Его можно разобрать и с помощью регулярных выражений, и даже просто разбив его на куски по символам перевода строк и пробелам. Но не будем забывать, что это лишь пример и выберем более серьезный путь.
Для автоматизации задач синтаксического и лексического анализа текста часто используют утилиты вроде Lex/Yacc. Недавно я писал о попытке реанимировать их версию для Delphi. И здесь они к месту.
Не буду пытаться объять необъятное, на просторах интернета множество руководств по использованию Lex и Yacc. Буду краток. С помощью данных утилит можно сгенерировать код парсера некоего языка по его формальному описанию. Собственно, они позволяют сгенерировать два куска кода: лексер (Lex) и парсер (Yacc). Лексер будет разбивать текст на токены, а парсер собирать эти токены в большую осмысленную программу.
Вполне очевидно, что для того, чтобы все это заработало, и токены, и структуру программы необходимо описать.
Описание токенов в нашем случае весьма однообразное. В языке программирования светофора есть ключевые слова RED, YELLOW, GREEN, ON, OFF, INIT, ENDINIT, LOOP, ENDLOOP, WAIT, а так же числа. Переводы строк и пробелы игнорируются. Ниже содержание файла описания токенов для Lex (как водится, слева регулярное выражение, справа шаблон Delphi-кода для его обработки).
D [0-9] %start %% var result : integer; {D}+(\.{D}+)?([Ee][+-]?{D}+)? begin val(yytext, yylval.yyCardinal, result); if result=0 then return(NUM) else return(ILLEGAL) end; RED begin return(RED) end; YELLOW begin return(YELLOW) end; GREEN begin return(GREEN) end; INIT begin return(INIT) end; ENDINIT begin return(ENDINIT) end; LOOP begin return(LOOP) end; ENDLOOP begin return(ENDLOOP) end; ON begin return(ON) end; OFF begin return(OFF) end; WAIT begin return(WAIT) end; " " ; . | \n ; \r ;
Затем нужно показать Yacc’у, по каким правилам из этих токенов формируется синтаксическое дерево программы (ниже фрагмент конфигурационного файла):
%token <Cardinal> NUM %token <TCustomLight> RED YELLOW GREEN %token <TEventList> INIT ENDINIT LOOP ENDLOOP %type <TCustomEvent> expr %type <TCustomLight> light %token ON OFF %token WAIT %token ILLEGAL /* illegal token */ %% input : /* empty */ | _init _loop { yyaccept; } ; _init : INIT exprs ENDINIT { FInitList.AddRange(FTemporaryList.ToArray); FTemporaryList.Clear; } ; _loop : LOOP exprs ENDLOOP { FLoopList.AddRange(FTemporaryList.ToArray); FTemporaryList.Clear } ; exprs : /* empty */ | exprs expr { FTemporaryList.Add($2); } ; expr : WAIT NUM { $$ := TWaitEvent.Create($2); } | light ON { $$ := TLightOnEvent.Create($1); } | light OFF { $$ := TLightOffEvent.Create($1); } ; light : RED { $$ := FRedLight; } | YELLOW { $$ := FYellowLight; } | GREEN { $$ := FGreenLight; } ;
Здесь видно, что есть тип токена light, который олицетворяет собой название конкретной лампочки. Тип expr, представляющий собой отдельное действие над лампой или задержку. А так же списки этих действий (exprs, на которых построены блоки _init и _loop). И опять же — слева описание, справа в скобках шаблон кода. Для программы из примера по этому описанию парсер может сформировать примерно вот такое синтаксическое дерево и обработать его:
На нижнем уровне находятся отдельные команды и их операнды, выше они постепенно объединяются в единое синтаксическое дерево. Проходя дерево «снизу вверх», останавливаясь в вершинах и выполняя код, указанный в шаблонах, парсер формирует результат. В нашем случае результат — это просто два списка команд (INIT и LOOP), эти списки затем используются для управления моделью светофора.
При должной сноровке подобные парсеры можно создавать очень и очень быстро. Не считая конфигурации утилит, в данной программе мною написано всего-лишь порядка 200 строк кода (включая модель светофора и код, который ей управляет). Это довольно мало, то есть разработка небольшого языка не требует больших затрат сил и времени. Хотя, конечно, необходима несколько большая квалификация, чем при работе с XML. Удобство пользователей обычно того стоит :)
Данный пост не претендует на то, чтобы быть полным руководством. Его цель продемонстрировать возможности утилит и относительную простоту разработки небольшого предметно-ориентированного языка, и всем этим, вероятно, заинтересовать тех, кто с этой темой в силу обстоятельств еще не сталкивался.
1. NameRec
29 Авг 2013 8:17 пп
Как-то интересно у Вас получается, Уважаемый автор…
Начали с конфигурационных файлов, указали на их «недостатки» в контексте задачи, которую обычно не описывают посредством конфигурационных файлов, а затем предложили «решение» — собственный DSL.
Нет, ну можно, конечно же и так… Но:
1. Вот такой XML:
выглядит несколько естественнее того, что Вы привели.
На мой вкус, впрочем.
Разумеется, я не стал бы рекомендовать использоваить конфигурационный файл для управлением светофором, но… В Вашей постановке это ещё возможно ;-)
2. Что же до программы на DSL, то на Python это будет, IMHO лаконичнее:
кроме того, не нужен никакой парсинг LEX/YACC, а язык — очень распространённый, на который имеются «тонны» документации.
3. Вообще же, тема DSL IMHO утратила актуальность с появлением XML, YAML/JSON с одной стороны, и встраиваемых скрипт-языков (Python, LUA) — с другой.
Общим недостатком всех DSL я вижу обратную сторону их достоинств, а именно — специализации на конкретике отдельной задачи.
До какого-то момента (пока язык простой) они помогают, но затем, по мере усложнения требований, усложняется и DSL, ввиду чего сдерживающим развитие проекта фактором становится уже он.
Поэтому я — за решения, использующие стандартные инструменты.
2. Роман Янковский
29 Авг 2013 9:57 пп
NameRec, спасибо за отзыв!
Все же я считаю, что если «читать» и «писать» код будут в основном вручную, то XML не очень подходит. Программно обрабатывать XML легко, но читаемость хромает, да и писать много лишнего приходится.
Python/Lua для означенных целей действительно могут хорошо подойти, но во-первых порой это из пушки по воробьям. Во-вторых, тут я не очень удачно выбрал пример, каюсь. У меня вместо языка конфигурации получился фактически маленький императивный язык программирования. Но если вернуться именно к вопросу описания конфигурации, то конфигурация обычно декларативна и тогда уже не так очевидно как можно применить Python.
Очень большое количество продуктов в *nix мире используют DSL, а не XML для описания конфигурации. Так что, по-моему, этот подход вполне имеет право на жизнь. Тем более, что это одна из традиционно сильных частей *nix.
P.S. Я исправил проблемы с отображением в первом комментарии, а второй удалил. Надеюсь, вы не против :)
3. NameRec
29 Авг 2013 11:19 пп
— Если кода много и/или если его будут именно *писать*, а не копировать существующий, то да, возможно.
Вместе с тем, есть ряд обстоятельств, которые IMHO следует принимать во внимание:
1. Есть средства автоматизации проверки XML (и, вроде бы, YAML) на корректность. Например, DTD.
2. Большинство IDE справляется не только с подсветкой синтаксиса XML, но и проверкой его корректности, что тоже немало.
3. XML/YAML это форматы разметки, обладающие очень широкими возможностями, но служащие, скорее, целям хранения данных, а не описания их средствами *вручную* чего-либо.
Таким образом, если потребуется изучить или, даже вручную поправить что-то, сформированное программно, то эти форматы весьма удобны. Поэтому и распространены они для хранения разного рода конфигураций.
— Почему?
Они очень легко встраиваются в приложения и потребляют относительно немного ресурсов. Особенно LUA, но в пользу Python говорит то, что он «поставляется с батарейками», которые превосходны.
Кроме того, у них чрезвычайно ёмкий синтаксис, функционально накрывающий любой DSL.
Они не привязывают пользователя к какой-либо парадигме (как Object Pascal или C++, к примеру) — в контексте охватывающего приложения на них можно программировать совершенно без фреймворков (исключая функциональность приложения, в моём случае это модуль trafficlight, реализованный, скажем, на Delphi) и совершенно процедурно.
— Вот тут, честно говоря, я понял Вас не вполне.
Возможно, потому, что я с довольно давних пор разделяю области применения декларативного и императивного подходы задавая себе вопрос о необходимости ветвлений. По простому: нужна ли обработка условий в описательном языке?
Если такая возможность подразумевается или, хотя бы, возможна, то я отдаю предпочтение императивному подходу, который означает применение скрипт-языка. Рассуждая подобным образом, я пришёл бы к выводу, что если управление светофором требуется предоставить пользователю, ему следует дать инструмент в виде скрипт-языка.
Если нет, причём желательно — «нет» по принципиальным соображениям, то предпочтение отдаётся декларативной форме — но опять же, описанной стандартными средствами (XML/JSON). Так, для хранения ресурсных файлов я предпочёл бы XML/JSON, а не «странные» ресурсы DFM или RC.
Например, в качестве формата для контейнеров Delphi (файлы DFM) я выбрал бы XML/JSON (что и сделала команда Lazarus).
Ну и напоследок, в этой части, хотел бы обратить внимание, что императивная форма, IMHO является более общей формой записи, нежели декларативная.
Т.е. то, что можно обозначить декларативно, можно сделать и императивно (пусть и не так изящно), но не наоборот.
Хотя повторюсь, что опасаюсь, что понял Вас неправильно.
— Да, я на это давно обратил внимание.
Но Вы знаете, мне это только создаёт проблемы :-( Особенно, в тех случаях, когда «мир *nix» соприкасается с «миром Windows».
Возьмём те же конфигурационные файлы. Недавно оказалась желательной обработка конфигурационных файлов Java-приложения в приложении на Delphi. Ну, просто потому, что у нас Delphi и Java приложения взаимодействуют и, грубо утрируя, следовало иногда проверять конфигурацию, в которой исполнялось Java-приложение. Выяснилось, что формат нестандартный, готовых инструментов обработки из Delphi нет, как и нет ничего такого в этих файлах, что нельзя было бы описать в терминах XML/JSON и, даже много более примитивного INI.
Понятно, что это значило дополнительное время на разработку, что не способствовало её удешевлению во всех смыслах.
Так что, IMHO подход имеет право на жизнь, но вот целесообразности в нём я особенной не вижу. Да и знаю достаточно хорошо на личном опыте, куда может завести свой собственный DSL…
Если интересно — могу рассказать :-)
— Конечно не против :-)
4. Роман Янковский
30 Авг 2013 11:00 дп
NameRec, трудно с вами спорить :)
Позиции универсальных средств довольно сильны, на то они и универсальные. Но все же я чувствую, что если ставить во главу угла не удобство программной обработки, а удобство работы конечных пользователей, то уже могут возникать варианты…
Но это ничего, давайте просто зафиксируем наши разногласия.
А про опыт расскажите конечно. Личный опыт — это всегда интересно.
5. Alex W. Lulin
30 Авг 2013 5:43 пп
http://18delphi.blogspot.com/2013/08/python.html
В общем я лично продолжаю оставаться на позиции, что XML да и Python — «в чистом виде» — не самое идеальное решение для DSL.
6. NameRec
31 Авг 2013 3:55 дп
NameRec:
Не в плане полемики, просто размышления насчёт DSL vs альтернативы.
«Но все же я чувствую, что если ставить во главу угла не удобство программной обработки, а удобство работы конечных пользователей, то уже могут возникать варианты…»
— Знаете… Некоторое время назад я задавался таким вопросом.
Рассуждая об удобстве «конечных пользователей», мне представляется, что неплохо бы обозначить, какие пользователи встречаются.
Исходя из собственного опыта мне видится вполне приемлемым такое условное разделение:
1. «Чистый пользователь». Т.е. тот, кто вводит или выполняет поиск информации с использованием относительно простых средств.
Такой пользователь, как правило, достаточно далёк от программирования во всех его проявлениях. Часто — по собственной инициативе дистанцируется от всего, с ним связаннрого.
Примеры: секретари, бухгалтеры, юристы, врачи, экономисты, сотрудники госучреждений, ну и т. д.
Таких пользователей — абсолютное большинство.
Разумеется, бывают исключения.
2. «Продвинутый пользователь». Т.е. пользователь, имеющий некоторые (именно — некоторые) представления о РБД и программировании.
Примеры: системные администраторы, сотрудники НИИ, студенты и ВУЗов естественных специальностей, инженеры, аналитики и т. д.
Таких пользователей гораздо меньше.
Думаю, в контексте темы «свой DSL» мы с Вами говорим о пользователе второго рода.
Если так, то каким должен быть язык, удовлетворяющий его потребностям?
Мне показались бы разумными следующие критерии:
1. Это должен быть очень простой язык, не требующий больших затрат на изучение.
2. Это должен быть достаточно распространённый язык, чтобы не было проблем с документацией. Здесь компания-разработчик может сэкономить на создании документации.
3. Это должен быть достаточно мощный язык, чтобы облегчить работу не только пользователям, но и сотрудникам компании-разработчика, чтобы удешевить программирование.
4. Этот язык должен содержать развитые средства отладки, поскольку программы на этом языке могут *неожиданно* стать большими.
5. Этот язык должен обеспечивать простоту добавления функциональности по взаимодействию с охватывающей, т.е. разрабатываемой системой, поскольку набор средств, предоставляемых пользователю может расшириться.
Причём важно, чтобы расширение такой функциональности можно было описывать с использованием современных парадигм (я имею ввиду, прежде всего, ООП).
Перечисленные критерии сделали для бессмысленной проработку темы собственного DSL.
Теперь относительно «удобства пользователя».
Вообще-то, это весьма размытый критерий. Но в любом случае, по удобству никакой DSL не сравнится с интерактивными средствами, такими, как мастера (wizards) например.
В принципе, если DSL сводится к простым командам, возможно — циклам и некоторым условиям, IMHO лучше разработать интерактивные средства с соответствующими возможностями, чем принуждать пользователя работать с исходным текстом на «простом» DSL.
Кому лучше? — Пользователю. Да и программисту тоже.
Поскольку первый избавится от ошибок компиляции, а второй избавится от необходимости реализовывать собственную исполняющую систему.
Таким образом, мне представляется, что в настоящий момент концепцию «собственного DSL» теснят встраиваемые скрипт-языки с одной стороны, и интерактивные средства — с другой.
7. Роман Янковский
31 Авг 2013 5:46 пп
Групп на самом деле не две, их намного больше :)
Если с программой работает не специалист в программировании, а специалист в конкретной предметной области, то для него намного естественнее написать «зеленый зажгись», чем разбираться во всяких там «from trafficlight import Trafficlight» и пытаться понять зачем там столько t с точкой. Вы так хвалите доступность документации Python, будто кто-то будет тратить время на его изучение и забивать себе голову всем этим ненужным им мусором. Им проще программиста нанять.
И вот чтобы этого избежать чаще всего и нужны DSL. Чтобы человек мог взаимодействовать с программой на языке предметной области, а не на чем-то как бы универсальном.
Вы предлагаете Python, потому что с ним знакомы. А случайный человек с улицы, глядя на предлагаемые вами удобные вам альтернативы, будет смотреть с недоумением. Пока вы ему будете объяснять что такое «класс» и показывать обширную документацию, он на моем DSL запрограммирует сотню-другую светофоров :)
Вот как-то так мне кажется.
Вот насчет wizard-ов вы верно заметили, чаще всего такого решения должно вполне хватить. Но по мере усложнения конфигураций текстовая форма начинает выигрывать.
Да и мы в общем-то до сих пор программируем в текстовом редакторе, несмотря на количество попыток разработать графическую среду программирования. Дальше детских игрушек и обработки частных случаев эти затеи пока не зашли.
8. NameRec
31 Авг 2013 8:56 пп
NameRec:
«Групп на самом деле не две, их намного больше :)»
— Здесь всё зависит от критерия разделения на группы.
Я использовал критерий знакомства пользователя с программированием.
Ведь достаточно просто ответить на вопрос, для кого мы проектируем систему: требуем мы от пользователя умение писать программы или нет?
Если да — вторая категория, если нет — первая.
«Если с программой работает не специалист в программировании, а специалист в конкретной предметной области, то для него намного естественнее написать «зеленый зажгись», чем разбираться во всяких там «from trafficlight import Trafficlight» и пытаться понять зачем там столько t с точкой.»
— Вне всякого сомнения :-)
Но в таком случае ещё более естественным для него будет выбрать команду из списка (ComboBox), нажать соответствующую кнопку в панели инструментов или перетащить команду мышью из одного списка в другой. После чего, например, в панели диалога задать параметры этой команды.
Для пользователя первого рода IMHO следует поступать именно так, а не вынуждать его что-то писать, пусть и по-русски, в текстовом окне.
«Вы так хвалите доступность документации Python, будто кто-то будет тратить время на его изучение и забивать себе голову всем этим ненужным им мусором…(и далее по тексту)»
— Простите, но ему всё равно придётся «забить голову» Вашими INIT/ENDINIT, LOOP/ENDLOOP (даже если Вы переведёте это на русский язык). В сущности — изучить синтаксис и семантику Вашего языка.
Так или иначе, но Вы вынуждаете пользователя программировать. Особенно, если принять во внимание эти Ваши слова: «по мере усложнения конфигураций текстовая форма начинает выигрывать».
Вот я и хочу понять, на кого ориентирован Ваш DSL: на пользователя-программиста? — Но тогда ему проще работать с полноценным языком с полноценной документацией.
Или пользователем, не имеющем о программировании понятия (как я понял, Вы подразумеваете именно такого) — но тогда зачем все эти тексты в текстовом редакторе, синтаксические ошибки, отладчик и пр.? Почему не дать ему интерактивный инструмент?
Кстати, этот инструмент также будет DSL, только не текстовым :-)
Апофеозом такого подхода, вероятно, является то, на чём коммунисты запустили «Буран»:
«Да и мы в общем-то до сих пор программируем в текстовом редакторе, несмотря на количество попыток разработать графическую среду программирования. Дальше детских игрушек и обработки частных случаев эти затеи пока не зашли.»
— Здесь Вы уж точно неправы… ;-)
http://ru.wikipedia.org/wiki/ДРАКОН.
К слову, ЭТО живёт и сейчас: http://drakon.su/
Впрочем… Всё, что происходит в нашей жизни — частный случай. Не так ли? ;-)
9. Alex W. Lulin
31 Авг 2013 11:33 пп
Позиция Романа — мне ближе.
10. Роман Янковский
1 Сен 2013 12:30 дп
NameRec,
Сходу могу предложить как минимум один пример, находящийся «между» этими группами. Это системные администраторы. Частенько не умея толком программировать (в промышленном смысле этого слова), временами они пишут весьма замысловатые конфигурационные файлы.
В целом же, я вижу, мы друг друга не переубедим. Ну и ладно :)
11. NameRec
1 Сен 2013 12:50 дп
NameRec:
«Сходу могу предложить как минимум один пример, находящийся «между» этими группами. Это системные администраторы. Частенько не умея толком программировать (в промышленном смысле этого слова), временами они пишут весьма замысловатые конфигурационные файлы.»
— Конкретно, приведённый Вами пример — не находится между группами, а однозначно относится ко второй по следующим причинам:
* Эти люди с высшим техническим образованием, в процессе обучения у них есть спецкурсы по программированию. Один из таких спецкурсов веду я, кстати.
* По роду службы эти люди имеют дело не только с конфигурационными файлами, но и со скриптами. Они умеют читать документацию, и скрипт-языки разработаны, в частности для них (те же shell-скрипты в *nix).
* Эти люди достаточно легко разбтираются с простыми языками, но боюсь, что не очень любят «засорять» голову чем-то совсем уж специализированным. Например, разбираться с PL/SQL они станут охотно, поскольку это полезно для карьеры, а вот со специализированным языком… Боюсь, что первое, что они запросят — это документацию к нему, а потом могут начать «фукать» по поводу того, что в «простом DSL» того нет, сего нет…
Это «элита» продвинутых пользователей, и плохой пример в данном случае.
«В целом же, я вижу, мы друг друга не переубедим. Ну и ладно :)»
— А нет у меня цели Вас переубеждать, Роман :-)
И Александра, кстати, тоже.
Вы выбрали путь — остаётся пожелать Вам на нём удачи.
Но мне представляется полезным, если я доведу до Вас альтернативный взгляд на обсуждаемые вопросы, принимая во внимание Вашу точку зрения.
12. Alex W. Lulin
1 Сен 2013 3:31 дп
http://ru.wikipedia.org/wiki/ДРАКОН.
Вы уж определитесь :-) Нужна графическая нотация или нет?
13. NameRec
1 Сен 2013 4:55 дп
NameRec:
«Вы уж определитесь :-) Нужна графическая нотация или нет?»
— Мне кажется, я уже отвечал на этот вопрос…
Графическая нотация уместна тогда, когда от неё больше пользы, чем вреда.
Это кстати, касается не только графической нотации, но и всего остального.
Если у Вас сложилось впечатление, что я «ярый противник» графической нотации — Вы ошиблись. Я просто считаю, что у неё есть своё место в процессе разработки. В моём представлении это место находится, прежде всего, в области проектирования и визуализации.
Далее, в моём представлении, графическая нотация хорошо выполняет роль DSL тогда, когда это не ведёт к усложнению предметного языка.