Примеры задач, в которых имеет смысл использовать DSL
25 сентября 2013
Сказать по правде, я долгое время не понимал, для чего нужны DSL. В конце концов, всю жизнь без них как-то обходился, и нормально. Какую проблему, спрашивается, они решают? И вообще, у нас тут безо всяких DSL сплошь и рядом зоопарки языков программирования, а вы предлагаете ввести еще один язык!? Но, подумав, поспрашивав людей и погуглив немного, я пришел к выводу, что в ряде случаев создание собственных DSL может быть хорошей идеей. Проще всего убедиться в этом, рассмотрев несколько практических задач и как они решаются с использованием DSL.
Дисклеймер: Получился какой-то дамп сознания, а не пост, так что, пожалуйста, простите мне некоторую вольность в трактовке вещей вроде «что считается DSL, а что нет». Если вы ищите более строгий материал по предметно-ориентированным языкам, то можете найти его в статье на англоязычной Википедии и далее по ссылкам.
В действительности, мы постоянно работаем с DSL, просто не всегда осознаем это. Несмотря на то, что предметно-ориентированные языки противопоставляются языкам программирования общего назначения, по факту, последнее тоже в существенной степени являются DSL. Например, Си представляет собой DSL для написания, например, всяких там драйверов, а PHP — DSL для написания сайтов. Doxygen, LaTeX, SQL, регулярные выражения, HTML, CSS, команды, которые мы вводим в bash, конфиги Nginx — все это тоже DSL, заточенные специально для решения определенного круга задач. Согласитесь, было бы странно верстать веб-страницы на Си, а запросы к базе данных формулировать на CSS.
Но все названное представляет собой готовые DSL. Зачем же изобретать новые? На самом деле, этого лучше не делать. Если для решения вашей задачи уже есть готовый, проверенный временем, стабильный DSL, нет повода не использовать его. Но иногда вы можете столкнуться с задачами, для решения которых готового DSL нет. Тут-то вам и пригодятся макросы Lisp, Template Haskell, так называемые компиляторы компиляторов и прочие средства для создания DSL.
Вообще-то говоря, для создания собственного DSL не обязательно прибегать к метапрограммированию или написанию компилятора. Когда вы определяете набор функций или классов для работы в определенной предметной области, вы тоже создаете DSL. Например, функции для работы с исключениями и транзакционной памятью в Haskell представляют собой полноценные предметно-ориентированные языки программирования. Если есть возможность найти эффективное решение задачи без использования метапрограммирования, обычно лучше пойти именно этим путем. Далее будет предполагаться, что это не представляется возможным по каким-то причинам.
Так что же это могут быть за задачи?
Допустим, вы в течение некоторого времени пилите некий RESTful сервис и обратили внимание, что пишите довольно много шаблонного кода. Например, у вас обычно есть некая табличка в базе данных, для которой вы пишите CRUD, притом во время чтения может производится фильтрация по извлекаемым из таблицы полям, передаваться limit, offset и другие параметры. Также от вас обычно требуется предусмотреть экспорт в CSV или еще какой-нибудь формат.
В этом случае вы можете внимательно посмотреть на уже написанный код, найти в нем повторяющиеся куски для разных ресурсов и написать DSL, который принимает на вход некое описание ресурса (то есть, частей кода, которые различаются) и на основе этого описания создает в вашем сервисе новый ресурс. Например, на входе вы можете указать URL ресурса, его краткое описание, перечень полей таблицы в БД, их типы, допустимые значения, значения по умолчанию и так далее, а на выходе получить документацию по API в Markdown, схему базы данных (для MySQL, PostgreSQL, SQLite и Oracle), код серверной части приложения с корректной валидацией и сообщениями об ошибках, а также код клиентов на десяти языках программирования.
Мы описываем ресурс в десяти строках и получаем тысячи, если не десятки тысяч, строк! How cool is that? Если подумать, DSL почти всегда является в том или ином виде попыткой избавиться от шаблонного кода. Например, CSS нужен для того, чтобы не вводить постоянно стили прямо в HTML, Doxygen — чтобы генерировать документацию на основе комментариев, SQL — чтобы не писать запросы в терминах хождения по индексам, кэшам и чтения блоков данных с диска, всякие высокоуровневые языки программирования — чтобы не управлять памятью вручную и не писать проверки на выход за границы массива, язык Си — чтобы не писать на ассемблере, ассемблер — чтобы не писать в байткодах, байткоды — чтобы не вбивать единички и нолики.
Веб-фреймворки по большому счету представляют собой DSL. Всякие ORM — это тоже DSL. Вы описываете базу данных, а в результате получаете SQL-запросы для ее инициализации, процедуры миграции от версии к версии, шаблонные функции типа create_user, user_exists и так далее. Другой вопрос, что некоторые ORM работают неправильно. Вместо того, чтобы описывать схему БД на неком DSL, они ходят в уже созданную базу, определяют ее схему и на ее основе генерируют наборы классов.
Вы можете написать DSL для описания логов системы. Этим вы не только приведете логи к одному строгому формату, но и сможете сгенерировать документацию по ним с описанием, какое сообщение в логах что означает, какая дополнительная информация пишется в логи с этим сообщением, куда звонить или писать, если такое сообщение появляется. Документация может генерироваться в множестве различных форматов (doc, pdf, html, markdown, …). Кроме того, вы можете реализовать проверки на этапе компиляции, что нигде в приложении в логи не пишется какая-нибудь ерунда.
Нетрудно представить аналогичный DSL для описания конфигурационных файлов.
При помощи DSL можно описывать форматы данных и протоколы, после чего генерировать на основе этого описания код для работы с этими протоколами и форматами. В том числе, код для сериализации и десериализации объектов в и из заданного формата, а также передачи сериализованных объектов по заданному протоколу. Другими словами, Protobuf тоже представляет собой DSL.
Различные DSL активно используются в Yesod, где с их помощью делаются такие классные вещи, как проверки на этапе компиляции, что приложение не сошлется на несуществующую веб-страницу, валидация HTML, CSS и JavaScript, компрессия CSS и JavaScript, парсинг файлов с переводами сайта на различные языки, компиляция HTML-шаблонов в машинный код, валидация форм (как на серверной стороне, так и на клиентской при помощи JavaScript) и не только.
DSL приходят на помощь, если вы решили расширить используемый вами язык программирования. С их помощью вы можете добавить в язык модель акторов и обмен сообщениями, изменяемые переменные, множественное наследование, контрактное программирование или даже какое-нибудь описание грамматик для генерации текстов.
Как видите, DSL можно применять для решения огромного множества задач. На самом деле, даже странно, что в третьем тысячелетии мы все еще пишем код для решения задач, а не исключительно код для написания кода для решения задач. Ведь в этом случае автоматически возрастает производительность программиста, уменьшается количество шаблонного кода, снижается вероятность возникновения ошибок, да и работа становится интересней.
А какие примеры использования DSL можете привести вы?
Метки: Разработка, Философия.
Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.