Что такое dsl в программировании
Перейти к содержимому

Что такое dsl в программировании

  • автор:

Kotlin DSL: Теория и Практика

Sql, RegExp, Gradle — что их объединяет? Всё это примеры использования проблемно-ориентированных языков или DSL (domain-specific language). Каждый такой язык решает свою узконаправленную задачу, например, запрос данных из БД, поиск совпадений в тексте или описание процесса сборки приложения. Язык Kotlin предоставляет большое количество возможностей для создания собственного проблемно-ориентированного языка. В ходе статьи мы разберемся, какие инструменты есть в арсенале программиста, и реализуем DSL для предложенной предметной области.

Весь синтаксис, представленный в статье, я объясню максимально просто, однако, материал рассчитан на практикующих инженеров, которые рассматривают Kotlin, как язык для построения проблемно-ориентированных языков. В конце статьи будут приведены недостатки, к которым нужно быть готовым. Используемый в статье код актуален для Kotlin версии 1.1.4-3 и доступен на GitHub.

Что такое DSL?

Языки программирования можно разделить на 2 типа: универсальные языки (general-purpose programming language) и предметно-ориентированные (domain-specific language). Популярные примеры DSL — это SQL, регулярные выражения, build.gradle. Язык уменьшает объем предоставляемой функциональности, но при этом он способен эффективно решать определенную проблему. Это способ описать программу не в императивном стиле (как нужно получить результат), а в декларативном или близком к декларативному (описать текущую задачу), в таком случае решение проблемы будет получено исходя из заданной информации.

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

В статье я рассмотрел построение «внутреннего» DSL на языке Kotlin. Такой вид проблемно-ориентированных языков реализуется на основе синтаксиса универсального языка. Подробнее об этом вы можете прочитать по ссылке.

Область применения

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

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

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

В ходе статьи мы разберемся в конструкции DSL для тестирования небольшой демонстрационной системы планирования занятий между учеником и преподавателем.

Основные возможности

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

Название функциональности DSL синтаксис Обычный синтаксис
Переопределение операторов collection += element collection.add(element)
Псевдонимы типа typealias Point = Pair<Int, Int> Создание пустых классов-наследников и прочие костыли
Соглашение для get/set методов map[«key»] = «value» map.put(«key», «value»)
Мульти-декларации val (x, y) = Point(0, 0) val p = Point(0, 0); val x = p.first; val y = p.second
Лямбда за скобками list.forEach

list.forEach(<. >)
Extention функции mylist.first(); // метод first() отсутствует в классе коллекции mylist Утилитные функции
Infix функции 1 to «one» 1.to(«one»)
Лямбда с обработчиком Person().apply

Нет
Контролирование контекста @DslMarker Нет

Нашли для себя что-то новое? Тогда продолжим.

В таблице намеренно пропущены делегированные свойства, так как, на мой взгляд, они бесполезны для построения DSL в том виде, который мы будем рассматривать. Благодаря указанным возможностям вы сможете писать код чище, избавиться от большого количества «шумного» синтаксиса и при этом сделать разработку еще более приятным занятием («куда уж приятнее?» — спросите вы). Мне понравилось сравнение из книги Kotlin in Action, в натуральных языках, например, в английском, предложения построены из слов и грамматические правила управляют тем, как нужно объединять слова друг с другом. Аналогично в DSL, одна операция может быть сложена из нескольких вызовов методов, а проверка типов обеспечит гарантию, что конструкция имеет смысл. Естественно, порядок вызовов может быть не всегда очевиден, но это остается на совести проектировщика DSL.

Важно понимать, что в этой статье мы будем рассматривать «внутренний DSL», т.е. проблемно-ориентированный язык базируется на универсальном языке — Kotlin.

Пример финального результата

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

Инструменты

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

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

Лямбда вне скобок

Лямбда-выражения или лямбды — это блоки кода, которые можно передавать в функции, сохранять или вызывать. В языке Kotlin тип лямбды обозначается следующим образом (список типов параметров) -> возвращаемый тип . Следуя этому правилу, самый примитивный вид лямбды это () -> Unit , где Unit — это аналог Void с одним исключением. В конце лямбды или функции мы не
должны писать конструкцию «return . ». Благодаря этому, мы всегда имеем возвращаемый тип, просто в Kotlin это происходит неявно.

Ниже приведен простейший пример того, как можно сохранить лямбду в переменную:

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

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

Базовый инструмент, который вы уже могли встретить, например, в Groovy, это лямбда вне скобок. Обратите внимание на пример в самом начале статьи, практически каждое использование фигурных скобок, за исключением стандартных конструкций — это использование лямбд. Существует как минимум два способа сделать конструкцию вида x < … >:

  • объект x и его унарный оператор invoke (этот способ обсудим позже);
  • функция x, в которую передают лямбду.

Независимо от варианта, мы используем лямбды. Допустим, есть функция x() . В языке Kotlin действует следующее правило: если лямбда является последним аргументом функции, то её можно вынести за скобки, если при этом лямбда единственный параметр, то скобки можно не писать. В результате, конструкция x(<…>) может быть преобразована в x() <> , а затем, убрав скобки, мы получаем x <> . Объявление такой функции выглядит следующим образом:

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

Но что если x — это экземпляр класса, объект, а не функция? Существует другое интересное решение, которое базируется на одной из основополагающих концепций, используемой при построении проблемно-ориентированных языков, переопределение операторов. Давайте рассмотрим этот инструмент.

Переопределение операторов

Kotlin предоставляет широкий, но ограниченный спектр операторов. Модификатор operator позволяет определять функции по соглашениям, которые будут вызываться при определенных условиях. Очевидным примером является функция plus, которая будет выполнена, при использовании оператора «+» между двумя объектами. Полный перечень операторов вы найдете по ссылке выше в документации.

Рассмотрим чуть менее тривиальный оператор «invoke». Главный пример этой статьи начинается с конструкции schedule < >. Назначение конструкции — обособить блок кода, который отвечает за тестирование планирования. Для построения такой конструкции используется способ, немного отличающийся от рассмотренного выше: оператор invoke + «лямбда вне скобок». После определения оператора invoke нам становится доступна конструкция schedule(. ), при том, что schedule — это объект. Фактически, вызов schedule(. ) интерпретируется компилятором как schedule.invoke(…). Давайте посмотрим на декларацию schedule.

Нужно понимать, что идентификатор schedule отсылает нас к единственному экземпляру класса schedule (синглтону), который помечен специальным ключевым словом object (подробнее о таких объектах, можно прочитать здесь). Таким образом, мы вызываем метод invoke у экземпляра schedule и при этом единственным параметром метода определяем лямбду, которую выносим за скобки. В итоге, конструкция schedule <… >равносильна следующей:

Однако если вы посмотрите внимательнее на метод invoke, то увидите не обычную лямбду, а «лямбду с обработчиком» или «лямбду с контекстом», тип которой записывается следующим образом: SchedulingContext.() -> Unit
Пора разобраться с тем, что это такое.

Лямбда с обработчиком

Kotlin дает нам возможность установить контекст для лямбда-выражения. Контекст — это обычный объект. Тип контекста опрелеляется вместе с типом лямбда-выражения. Такая лямбда приобретает свойства нестатического метода в классе контекста, но с доступом только к публичному API этого класса.
В то время как тип обычной лямбды определяется так: () -> Unit , тип лямбды с контекстом типа Х определяется так: X.()-> Unit и, если первый тип лямбд можно запускать привычным образом:

то для лямбды с контекстом нужен контекст:

Напомню, что в объекте schedule у нас определен оператор invoke (см. предыдущий параграф), который позволяет нам использовать конструкцию:

Лямбда, которую мы используем, имеет контекст типа SchedulingContext. В этом классе определен метод data. В результате у нас получается следующая конструкция:

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

Чтобы детально понять как работает этот пример, давайте уберем весь синтаксический сахар:

Как вы видите, всё предельно просто.
Давайте взглянем на реализацию оператора invoke.

Мы вызываем конструктор для контекста: SchedulingContext() , а затем на созданном объекте (контексте) вызываем лямбду с идентификатором init, которую мы передали в качестве параметра. Это очень похоже на вызов обычной функции. В результате, в одной строке SchedulingContext().init() мы создаем контекст и вызываем переданную в оператор лямбду. Если вас интересуют другие примеры, то обратите внимание на методы apply и with из стандартной библиотеки Kotlin.

В последних примерах мы рассмотрели оператор invoke и его взаимодействие с другими инструментами. Далее мы сфокусируемся на другом инструменте, который формально является оператором и делает наш код чище, а именно на соглашении для get/set методов.

Соглашение для get/set методов

При разработке DSL мы можем реализовывать синтаксис доступа к ассоциативному массиву по одному или более ключам. Взглянем на пример ниже:

Чтобы использовать квадратные скобки, необходимо реализовать методы get или set в зависимости от того, что нужно (чтение или запись) с модификатором operator. Пример реализации этого инструмента вы можете найти в классе Matrix на GitHub по ссылке. Это простейшая реализация обертки для работы с матрицами. Ниже часть кода, которая интересует нас.

Типы параметров функций get и set ограничены только вашей фантазией. Вы можете использовать как один, так и несколько параметров для get/set функций и обеспечивать комфортный синтаксис для доступа к данным. Операторы в Kotlin привносят много интересных возможностей, с которыми вы можете ознакомиться в документации.

К удивлению, в стандартной библиотеке Kotlin есть класс Pair, но почему? Большая часть сообщества считает, что класс Pair — это плохо, с ним пропадает смысл связи двух объектов и становится не очевидно, почему они в паре. Следующие два инструмента демонстрируют, как можно и осмысленность пары сохранить, и не создавать лишние классы.

Псевдонимы типа

Представим, что нам нужен класс-обертка для точки на плоскости с целочисленными координатами. В принципе, нам подходит класс Pair<Int, Int> , но в переменной такого типа в один момент мы можем потерять понимание того, зачем мы связываем значения в пару. Очевидные пути исправления — это либо писать собственный класс, либо еще, что похуже. В Kotlin арсенал разработчика пополняется псевдонимами типа, которые записываются следующим образом:

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

Однако у класса Pair есть два свойства, first и second, и как бы нам переименовать эти свойства так, чтобы стереть всякие различия между желаемым классом Point и Pair? Сами свойства переименовать не удастся, но в нашем инструментарии есть замечательная возможность, которую народные умельцы обозначили как мульти-декларации.

Мульти-декларации (Destructuring declaration)

Для простоты понимания примера рассмотрим ситуацию: у нас есть объект типа Point, как мы знаем из примера выше, это всего лишь переименованный тип Pair<Int, Int> . Как видно из реализации класса Pair стандартной библиотеки, он помечен модификатором data, а это значит, что, среди прочего, в данном классе мы получаем сгенерированные методы componentN. Давайте о них и поговорим.

Для любого класса мы можем определить оператор componentN, который будет предоставлять доступ к одному из свойств объекта. Это означает, что вызов метода point.component1 равносилен вызову point.first. Теперь разберемся, зачем нужно это дублирование.

Что такое мульти-декларации? Это способ «разложить» объект по переменным. Благодаря этой функциональности, мы можем написать следующую конструкцию:

У нас есть возможность объявить сразу несколько переменных, но что окажется в качестве значений? Именно для этого нам и нужны генерируемые методы componentN , в соответствии с порядковым номером, вместо N, начиная с 1, мы можем разложить объект на набор его свойств. Так, например, запись выше эквивалентна следующей:

что в свою очередь равносильно:

где first и second это свойства объекта Point.

Конструкция for в Kotlin имеет следующий вид, где x последовательно принимает значения 1, 2 и 3:

Обратим внимание на блок assertions в DSL из основного примера. Для удобства часть его я приведу ниже:

Теперь всё должно быть очевидно. Мы перебираем коллекцию scheduledEvents, каждый элемент которой раскладывается на 4 свойства, описывающие текущий объект.

Extension функции

Добавление собственных методов к объектам из сторонних библиотек или добавление методов в Java Collection Framework — давняя мечта многих разработчиков. И теперь у всех нас есть такая возможность. Объявление расширяющих функций выглядит следующим образом:

В отличии от обычного метода, мы добавляем название класса перед названием метода, чтобы обозначить какой именно класс мы расширяем. В примере AvailabilityTable это псевдоним для типа Matrix и, так как псевдонимы в Kotlin это только переименование, то в результате такая декларация равносильна приведенной в примере ниже, что не всегда удобно:

Но, к сожалению, ничего с этим поделать нельзя, кроме как не использовать инструмент или добавлять методы только в определенный класс контекста. Тогда магия появляется только там, где она нужна. Более того, вы можете расширять этими функциями даже интерфейсы. Хорошим примером будет метод first, расширяющий любой Iterable объект следующим образом:

В итоге, любая коллекция, основанная на интерфейсе Iterable, вне зависимости от типа элемента, получает метод first. Интересно то, что мы можем поместить extension метод в класс контекста и благодаря этому иметь доступ к расширяющему методу только в определенном контексте (см. выше лямбда с контекстом). Более того, мы можем создавать extension функции и для Nullable типов (объяснение Nullable типов выходит за рамки статьи, но при желании вы можете почитать здесь). Например, функция isNullOrEmpty из стандартной библиотеки Kotlin, которая расширяет тип CharSequence?, может быть использована следующим образом:

Сигнатура этой функции представлена ниже:

При работе из Java с такими Kotlin функциями, extension функции доступны как статические.

Infix функции

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

Такая конструкция эквивалентна следующей:

Есть ситуации, когда скобки и точки излишни. Именно на этот случай нам нужен infix модификатор для функций.
В коде выше, конструкция teacherSchedule[day, lesson] возвращает элемент расписания, а функция shouldNotEqual проверяет, что элемент не равен null.

Чтобы объявить такую функцию необходимо:

  • указать модификатор infix;
  • определить ровно один параметр.

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

Обратите внимание, что дженерик тип по умолчанию наследник Any (не Nullable иерархии типов), однако, в таких случаях, мы не можем использовать null, по этому необходимо явно указать тип Any?

Контроль контекста

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

До версии Kotlin 1.1 уже существовал способ, как этого избежать. Создание собственного метода data во вложенном контексте DataContext, а затем пометка его аннотацией Deprecated с уровнем ERROR.

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

В Kotlin 1.1 появился новый инструмент для контроля — аннотация @DslMarker. Она применяется на ваши собственные аннотации, которые, в свою очередь, нужны для маркирования ваших контекстов. Создадим свою аннотацию, которую пометим с помощью нового инструмента в нашем арсенале:

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

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

В этом примере у нас появляется третий уровень вложенности и вместе с ним новый контекст Student, который, на деле, сущностной класс, часть модели, а значит нам нужно пометить аннотацией @MyCustomDslMarker еще и сущностную модель, что, на мой взгляд, не верно.

В контексте Student вызовы data <> всё так же запрещены, т.к. внешний DataContext никуда не делся, но эти конструкции остаются валидны:
javaschedule < data < student < student < >> > >

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

Использовать дополнительный контекст для создания студента, например, StudentContext. Это похоже на безумие и перестает оправдывать преимущества @DslMarker.

Создать интерфейсы для всех сущностей, например, IStudent (наименование здесь не важно), создать контексты-пустышки, наследующие эти интерфейсы, и делегировать реализацию объектам студентов, что тоже на грани бреда.
@MyCustomDslMarker
class StudentContext(val owner: Student = Student()): IStudent by owner

Воспользоваться аннотацией @Deprecated, как в примерах выше. В данном случае, пожалуй, это лучшее решение, которым можно воспользоваться.
Просто добавляем deprecated extension метод для всех Identifiable объектов.

В итоге, комбинируя разные инструменты, мы строим комфортный DSL для решения наших задач.

Минусы использования DSL

Попытаемся быть более объективными в применении DSL на Kotlin и разберемся, какие минусы есть у использования DSL в вашем проекте.

Переиспользование части DSL

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

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

This, it!?

Крайне легко потерять смысл текущего this и it в ходе взаимодействия со своим DSL. Если вы где-то используете it, как название параметра по умолчанию, и осознаете, что осмысленное название для этого параметра будет лучше, то просто сделайте это. Лучше немного очевидного кода, чем много неочевидных багов.

Наличие контекста может сбить с толку человека, который с ними никогда не работал. Однако теперь в вашем арсенале есть «лямбда с контекстом» и вас стало еще труднее поставить в тупик появлянием странных методов внутри DSL. Помните, что на крайний случай вы можете присвоить контекст переменной, например, val mainContext = this

Вложенность

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

https://amdy.su/wp-admin/options-general.php?page=ad-inserter.php#tab-8

Где доки, Зин?

Если вы когда-либо подступались к чужому DSL, то у вас наверняка вставал вопрос: «Где документация?». На этот счет у меня есть свое мнение. Если вы пишете DSL, который будет использован не только вами, то лучшей документацией будут примеры использования. Сама по себе документация важна, но скорее в качестве дополнительной справки. Смотреть её довольно неудобно, т.к. наблюдатель проблемно-ориентированного языка задается естественным вопросом: «Что мне нужно вызвать, чтобы получить результат?» и, по моему опыту, здесь эффективнее всего себя показывают примеры использования для схожих ситуаций.

Заключение

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

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

Потренируйтесь «на кошках», как герой одного известного фильма, сделайте DSL для тестов, а затем, сделав множество ошибок, и после появления опыта, рассмотрите и другие применения.
Желаю успехов в разработке проблемно-ориентированных языков!

What developers need to know about domain-specific languages

Various programming languages in use

A domain-specific language (DSL) is a language meant for use in the context of a particular domain. A domain could be a business context (e.g., banking, insurance, etc.) or an application context (e.g., a web application, database, etc.) In contrast, a general-purpose language (GPL) can be used for a wide range of business problems and applications.

A DSL does not attempt to please all. Instead, it is created for a limited sphere of applicability and use, but it’s powerful enough to represent and address the problems and solutions in that sphere. A good example of a DSL is HTML. It is a language for the web application domain. It can’t be used for, say, number crunching, but it is clear how widely used HTML is on the web.

A GPL creator does not know where the language might be used or the problems the user intends to solve with it. So, a GPL is created with generic constructs that potentially are usable for any problem, solution, business, or need. Java is a GPL, as it’s used on desktops and mobile devices, embedded in the web across banking, finance, insurance, manufacturing, etc., and more.

Classifying DSLs

In the DSL world, there are two types of languages:

  • Domain-specific language (DSL): The language in which a DSL is written or presented
  • Host language: The language in which a DSL is executed or processed

A DSL written in a distinct language and processed by another host language is called an external DSL.

This is a DSL in SQL that can be processed in a host language:

For that matter, a DSL could be written in English with a defined vocabulary and form that can be processed in another host language using a parser generator like ANTLR:

If the DSL and host language are the same, then the DSL type is internal, where the DSL is written in the language’s semantics and processed by it. These are also referred to as embedded DSLs. Here are two examples.

    A Bash DSL that can be executed in a Bash engine:

This is valid Bash that is written like English.

This uses a fluent style and is readable like English.

Yes, the boundaries between DSL and GPL sometimes blur.

DSL examples

Some languages used for DSLs include:

  • Web: HTML
  • Shell: sh, Bash, CSH, and the likes for *nix; MS-DOS, Windows Terminal, PowerShell for Windows
  • Markup languages: XML
  • Modeling: UML
  • Data management: SQL and its variants
  • Business rules: Drools
  • Hardware: Verilog, VHD
  • Build tools: Maven, Gradle
  • Numerical computation and simulation: MATLAB (commercial), GNU Octave, Scilab
  • Various types of parsers and generators: Lex, YACC, GNU Bison, ANTLR

Why DSL?

The purpose of a DSL is to capture or document the requirements and behavior of one domain. A DSL’s usage might be even narrower for particular aspects within the domain (e.g., commodities trading in finance). DSLs bring business and technical teams together. This does not imply a DSL is for business use alone. For example, designers and developers can use a DSL to represent or design an application.

A DSL can also be used to generate source code for an addressed domain or problem. However, code generation from a DSL is not considered mandatory, as its primary purpose is domain knowledge. However, when it is used, code generation is a serious advantage in domain engineering.

DSL pros and cons

On the plus side, DSLs are powerful for capturing a domain’s attributes. Also, since DSLs are small, they are easy to learn and use. Finally, a DSL offers a language for domain experts and between domain experts and developers.

On the downside, a DSL is narrowly used within the intended domain and purpose. Also, a DSL has a learning curve, although it may not be very high. Additionally, although there may be advantages to using tools for DSL capture, they are not essential, and the development or configuration of such tools is an added effort. Finally, DSL creators need domain knowledge as well as language-development knowledge, and individuals rarely have both.

DSL software options

Programming and development

Open source DSL software options include:

  • Xtext: Xtext enables the development of DSLs and is integrated with Eclipse. It makes code generation possible and has been used by several open source and commercial products to provide specific functions. MADS (Multipurpose Agricultural Data System) is an interesting idea based on Xtext for «modeling and analysis of agricultural activities» (however, the project seems to be no longer active).
  • JetBrains MPS: JetBrains MPS is an integrated development environment (IDE) to create DSLs. It calls itself a projectional editor that stores a document as its underlying abstract tree structure. (This concept is also used by programs such as Microsoft Word.) JetBrains MPS also supports code generation to Java, C, JavaScript, or XML.

DSL best practices

Want to use a DSL? Here are a few tips:

  • DSLs are not GPLs. Try to address limited ranges of problems in the definitive domain.
  • You do not need to define your own DSL. That would be tedious. Look for an existing DSL that solves your need on sites like DSLFIN, which lists DSLs for the finance domain. If you are unable to find a suitable DSL, you could define your own.
  • It is better to make DSLs «like English» rather than too technical.
  • Code generation from a DSL is not mandatory, but it offers significant and productive advantages when it is done.
  • DSLs are called languages but, unlike GPLs, they need not be executable. Being executable is not the intent of a DSL.
  • DSLs can be written with word processors. However, using a DSL editor makes syntax and semantics checks easier.

If you are using DSL now or plan to do so in the future, please share your experience in the comments.

OpenStack source code (Python) in VIM

Top 10 Vim plugins for programming in multiple languages

Make your life as a programmer or sysadmin a little better with these 10 plugins for Vim.

Why and how to handle exceptions in Python Flask

Why developers like to code at night

A nocturnal work schedule is the key to creativity and productivity for many open source programmers.

Taking the complex and making it simple: what is DSL, and why you need a new programming language

Hello, everybody, my name is Vladimir Kozhayev, I am a freelance tools developer (may accidentally sound like Alcoholics Anonymous club introduction). This article launches a sequence of materials dedicated to the development of parsers, translators, compilers, and similar instruments designed to make our lives simpler.

Indeed, why would we need one more programming language? Sure, it may be appreciated in some university research institutes, but how can this crafty highbrow stuff be applied in and help businesses? At the end of the day, what for do we need such a mind-blowing variety of languages? Let’s sort it out.

Why not use one language?

Once upon a time there was an English mathematician, computer scientist, logician, and cryptanalyst, named Alan Turing. Among many of his inventions people would probably instantaneously recognize the machine that bears his name. Without going into detail, let’s say, his machine can implement the same tasks that are implemented using some more complex, higher level programming tools. This means, any program in any language can be rewritten with the help of this relatively simple tool. This very machine can be implemented in any language of Java or PHP type.

As a result we end up with Turing complete. A language is regarded as complete if it can be used to actualize a Turing machine. All the popular programming languages of general purpose (Java, C#, PHP, Python, Scala, JavaScript and so on) are complete. What does it mean? All the popular languages are equivalent! We know that all programs can be run with the help of a machine. A machine that runs this program can be written in PHP and in C++. It’s true that the same program can be run anywhere if it is written in the language of a Turing machine. Besides, we know that any program can be written this way.

In practice it means that a program written in JavaScript, for instance, with a certain amount of effort can be rewritten in С++ and the other way around. No doubt, libraries and frameworks are important, yet the logics can be transferred from one language into another.

So, why do we need all these languages, why can’t we use just one? It occurred to me that in his science fiction novel “Babel-17” Samuel Delany described a man with one part of the brain switched off. The man is taught an artificial language instead, that is similar to mathematical expressions in its syntax. It is perfectly tuned to solve logic problems quickly, its compact, it has a high usability but its limited. For instance, such words as “me” and “you” were out, which means some logical mysteries like “The Barber’s paradox” can’t be processed by the restricted mind. They get burnt or redirected to the brain part which is switched off. In other words, the language constructions can predetermine the thinking pattern.

Let’s dwell on the language of mathematics. For example, here is the theorem proof description by math induction method:

Now, let’s decipher it. Supposing that:

  1. It is found that P1is true. (this statement is called induction base)
  2. It is proved for any n, if Pn is true, Pn+1 is also true. (this statement is called induction step)

Then, all the statements of our sequence are correct. Please, pay attention: instead of a few strings of text we have only one string with a rigorous definition, which is clear to any mathematician.

What is DSL

Domain Specific Language is a computer language specialized to a particular application domain. Its structure or data structure reflects the specifics of the problems it solves© Wikipedia.

In other words, if a specialist knows his specific domain, he doesn’t have to learn DSL — it is enough just to look at it (see the example with math). A quality DSL does not require extensive knowledge in programming theory and practice. Many DSLs do not have cycles. Some of them do not have “if” operator. A language can often prove to be Turing incomplete, which means you can’t use it to code any program. And again, let’s take a math language or language of quantifiers as an example. It is used only for theorem description or for their automatic proof. It would be really difficult to apply it for coding web-services.

DSL use cases

DSL application fields are quite varied. Let’s consider a few of them to understand when DSL should be used.

Laser cutter

Supposing you are a marine engineer and you want to cut a huge part for the ship hull. In the past people did it the following way: they contoured details on a tough board or plywood, cut them out, and applied them to a steel sheet. Afterwards, specially trained people called marking-off professionals were crawling on this steel sheet stamping in pits on the line of the cut. Then torch operator would go cutting down the contour line. Can you imagine if the torch operator has a horrible hangover in the morning?

A robot can do it automatically, instead. A robot has not issues with precision: it reads from the design and moves on the steel shit cutting out a required part. Yes, it is possible! But the thing is that the trajectory of the movement has to be assigned. It requires the following commands:

  1. To move from point А to point B with the cutter off (bearing in mind that a straight line is the shortest distance between two points).
  2. To move with the cutter on along the predetermined curve (straight line is a special case). To keep things simple, we speak here about a straight line piece and an arc of circumference with radius and center defined. As the position of the cutter is predetermined by the previous movements we need to set only the point of a full stop. The full stop point for the straight line segment is the end of the segment. The full stop point for the circle arc is rotation angle and center of circle.

Therefore, in the simple case we need only three commands:

As you can see, the language is simple but it can be applied to cut a part of any complexity. One only needs confident PC user skills in order to code with this language. By adding variables, conditional operators, cycles and procedures, we eventually come up with a powerful tool. As a bonus we can have code research using white box technique. For example, we can check if the cutter trajectory happens to go beyond the metal sheet edges.

Algorithmic tradingTrader seldom makes a mistake twice — usually three or more times© Sad experience of a man who sold his apartment

Everyone wants to buy cheaper and sell more expensive, everything is clear here. But how do we define the right time to close the deal if any day the price may rise or fall? The decision is made through fundamental evidence (news, analysis of economic and political trends) and technical evidence (projection of resource costs based on previous data available). Indicators that are now used to predict the price behavior are not proved theoretically and they lack accuracy. There is a certain connection to reality; however, usually the decision to open or close a deal is made judging by a few indicators. So, when all of them give a signal about a sale or a purchase, the decision can be made.

A price curve changes rapidly. The data obtained 15 min ago can be relevant only to historians. The money people work with on the stock market is enormous so it is easy to lose a few hundred million dollars in a matter of minutes. Therefore, it makes sense to minimize the human factor. But how can we do it if there is no particular price behavior theory, and a trader uses his intuition to choose a trading strategy? One of the ways to avoid mistakes is to create a special language with as little “background noise” as possible, while getting rid of all the impractical options and retaining only the necessary components in the language. What do we need for trading?

  1. Well, it doesn’t take a genius to understand that the price of snow in Antarctic and at the Equator would be really different. So, we need to specify the stock market where we analyze prices.
  2. We define a strategy of trading (there is a variety of them).
  3. We need to set the parameters specific to every stock market, as well as the time slot for the work.
  4. Strategies are launched on servers, every one of them working with a specified stock exchange. We need to set the time frame within which they work, since the access to stock markets may not be free.

Let’s take a look at this strategy when performed in Java. Supposing we want to receive signals every time currencies are bought or sold on all three servers.

We use the strategy: “Fibonacci”, “sliding average”, “the Hilbert transform”. To keep things simple, let’s assume the time is measured in ticks, the name field of the stock market where the server operates is set as a value of a string type; we trade in and exchange a few currencies such as dollars, euro and some else.

At a first glance the code looks okay, but if you just look closer you will notice a number of blunders.

Firstly, the operation time of strategies is shorter than the operation time of a server. Secondly, we start only one server, instead of three. So, using this code means financial loss is inevitable.

On the other hand, trading is a nonstop stress and it’s a race. It requires speedy work which has to be error free. How to achieve this level?

Let’s see how the program text in the above format might look like, if it was coded in a more practical and readable language.

The program start mark is at the top, the list of servers follows. The name of a stock market is set one time for every server. The time of work for server is also set one time. Then a strategy list follows where each strategy has its specific parameters.

The advantages of the example provided are obvious. The text has been streamlined: servers are set only one time. Strategies are in close proximity to a server where they are run. Secondly, we dispose of extra details: trader doesn’t have to know what Thread is or that the final program will be coded in Java.

Game logic development

Error: robot dies when a grenade hits (exactly when it hits, not when the grenade detonates). D — designer, P — programmer.

D: programmers have spoilt the whole thing! why?!

P: the weight of the grenade is 100kl! Why did you do it?

D: really?! The grenade has to sink in water!

P: why doesn’t it sink with a proper weight?

D: water has a high density! (note: higher than mercury) P: what do you mean higher than water?! It’s the impossibility!

D: wooden boxes have to float!

P: they can’t float any other way, can they?!

D: their weight is 50 kl!

D: other way they wouldn’t be falling apart so beautifully!

Supposing, you are a game designer and you need to create a behavior script for a robot. First, the robot shoots, and then when it is out of ammo, it runs. Programmers will implement it easily. But in a certain moment the behavior has to be changed, so that robot runs, and only starts shooting when he is nearly caught. Robot can also hide cozily behind the hill and move his ears in a sweetest manner. The programmer will do it as well, but it’s a loss of valuable time. There will be multiple fixings again and again and the programmer will have to be disturbed again and again.

Moreover, new games are released for numerous different platforms. One that is made for Windows can be in demand for Vii, tablets, smart TV and so on. Each release leads to rewriting the code, which has been tested and is already working, although the logic of actions of the characters does not change in general (when moving from device to device.). One may use cross-platform tools, such platforms as Unity or Haxe, but the problem stems from the fact that cross platform shows equally poor performance on all devices. What I’m trying to say is that it would be great if the logic could stay unaltered while we only change specific things for a specific platform.

We can also use script languages for logic, but they are also too complex to be used without properly learning them first. They have many details important for the work of a developer, but aren’t necessary for constructions like: “If this thing happened — do that thing”.

You can’t make a designer learn programming, right? These fields are so distant from one another, and in many terms they are opposite. Sure, one would like to have a designer who can change behavior of game characters without having to disturb a developer.

FSA

Let’s consider the game logic through the character’s conditions and transitions between them. For instance, a robot can have three states: “run to a player”, “shoot” and “look for the ammo”, when it got short of supplies. Actions happen when a condition is entered, exited, during a transition between conditions and when the condition between actions hasn’t changed. Transitions and states can be described with the help of JSON or XML; then we can use the coding pattern “state machine”, as described in “the gang of four”. XML for description is presented below:

< state name=”run_to_enemy”>

< before methods=”do_something_before”/>

< after methods=”say_hi”/>

< in_process methods=”say_hug”/>

< transitions>

< transition name=”shoot” methods=”run”>

< condition function=”near_the_enemy && have_bullets”/>

</ transition>

</ transitions>

</ state>

< state name=”shoot”>

< before methods=”do_something_before_shoot”/>

< after methods=”say_hia”/>

< in_process methods=”say_bum”/>

< transitions>

< transition name=”run_to_bullets” methods=”hi”>

< condition function=”no_bullets”/>

</ transition>

</ transitions>

</ state>

< state name=”run_to_bullets”>

< before methods=””/>

< after methods=””/>

< in_process methods=”run”/>

< transitions>

< transition name=”run_to_enemy” methods=”eat_bullets”>

< condition function=”near_the_bullets”/>

</ transition>

</ transitions>

</ state>

Yet, XML is so inconvenient for programming. Let’s take a look how to describe it with the help of DSL-language.

As you see, the description has got more streamlined and just more readable. We can see syntax highlighting now. By the way, it also features auto complete and error highlighting.

This way, we can distinguish between the game logic and the things that are determined by and depend on the platform: visuals, IO, and controlling. We can even distinguish it from how methods of “shoot”, “run” and “shout” are implemented in practice. The latter is a specific case of declarative programming: instead of implementing a detailed algorithm, we give the final result. Instead of telling a computer how to do something, we tell what to do.

BOTTOM LINE

All DSL analyzed here have the following features:

  1. They are not big and do not require massive investments. It is right to say so on the whole: domain specific language with a high barrier to entry is not a good one.
  2. They allow operation with the terms of the subject domain, without the specifics of software implementation. It’s like saying WHAT to do instead of HOW.
  3. They make it unnecessary for a specialist to acquire a high qualification in programming.

DSL is applied in cases when rather complex logic is to be written. A specialist in a specific domain is liberated of the necessity to study programming while a programmer is liberated of the necessity to be well-versed in a subject. And again, if to implement a task one doesn’t need a qualification beyond programming, one doesn’t need DSL.

My second article will dwell on visual languages, while in the following ones we will consider the ways of DSL actualization and their application in various, sometimes unexpected, fields.

Доменно-ориентированный язык

A предметный язык (DSL ) — это компьютерный язык специализированный для конкретного приложения домен. Это контрастирует с языком общего назначения (GPL), который широко применим во всех доменах. Существует широкий спектр DSL, начиная от широко используемых языков для общих доменов, таких как HTML для веб-страниц, до языков, используемых только одним или несколькими частями программного обеспечения, такими как MUSH программный код. DSL могут быть дополнительно подразделены по типу языка и включают в себя предметно-ориентированные языки разметки, предметно-ориентированные языки моделирования (в более общем смысле, языки спецификации ), и предметно-ориентированные языки программирования. Специализированные компьютерные языки всегда существовали в компьютерную эру, но термин «предметно-ориентированный язык» стал более популярным из-за развития предметно-ориентированного моделирования. Более простые DSL, особенно те, которые используются одним приложением, иногда неофициально называют мини-языками .

Граница между языками общего назначения и предметными языками не всегда четкая, поскольку язык может иметь специализированные функции для конкретная область, но может применяться более широко, или наоборот, в принципе может иметь широкое применение, но на практике используется в основном для конкретной области. Например, Perl изначально разрабатывался как язык обработки текста и склейки для той же области, что и AWK и сценарии оболочки, но в основном использовался как позже язык программирования общего назначения. В отличие от этого, PostScript является полным по Тьюрингу языком и в принципе может использоваться для любых задач, но на практике он узко используется как язык описания страниц.

Содержание

  • 1 Использование
  • 2 Обзор
    • 2.1 При проектировании и реализации
    • 2.2 Инструменты программирования
    • 3.1 Внешние и встроенные специфичные для домена языки
    • 3.2 Шаблоны использования
    • 3.3 Цели разработки
    • 3.4 Идиомы
    • 4.1 Язык GameMaker
    • 4.2 Скрипты оболочки Unix
    • 4.3 Язык разметки ColdFusion
    • 4.4 Erlang OTP
    • 4.5 FilterMeister
    • 4.6 Шаблоны MediaWiki
    • 4.7 Разработка программного обеспечения использует
    • 4.8 Метакомпиляторы
    • 4.9 Unreal Engine до версии 4 и другие игры
    • 4.10 Механизмы правил для автоматизации политик
    • 4.11 Языки статистического моделирования
    • 4.12 Создание моделей и сервисов на несколько языков программирования
    • 4.13 Gherkin
    • 4.14 Другие примеры

    Использование

    Разработка и использование соответствующих DSL является ключевой частью домена Engineering с использованием языка, подходящего для данной области — это может состоять из использования существующего DSL или GPL или разработки нового DSL. Языко-ориентированное программирование рассматривает создание специализированных языков для выражения проблем как стандартную часть процесса решения проблем. Создание предметно-ориентированного языка (с программным обеспечением для его поддержки), а не повторное использование существующего языка, может иметь смысл, если язык позволяет выразить конкретный тип проблемы или решения более четко, чем это позволяет существующий язык, и тип Проблема, о которой идет речь, возникает достаточно часто. С практической точки зрения, DSL может быть специализирован для конкретной проблемной области, конкретной техники представления проблемы, конкретной техники решения или других аспектов области.

    Обзор

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

    • предметно-ориентированный язык для полисов страхования жизни, разработанный внутри крупного страхового предприятия
    • предметно-ориентированный язык для моделирования боевых действий
    • предметно-ориентированный язык для расчета заработной платы
    • предметно-ориентированный язык для выставления счетов

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

    В дизайне и реализации

    предметно-зависимые языки — это языки (или часто объявленные синтаксисы или грамматики) с очень конкретными целями в дизайне и реализации. Доменно-ориентированный язык может быть одним из языков визуальных диаграмм, например, созданных с помощью Generic Eclipse Modeling System, программных абстракций, таких как Eclipse Modeling Framework, или текстовых языков.. Например, служебная программа командной строки grep имеет синтаксис регулярного выражения, который соответствует шаблонам в строках текста. Утилита sed определяет синтаксис для сопоставления и замены регулярных выражений. Часто эти крошечные языки могут использоваться вместе внутри оболочки для выполнения более сложных задач программирования.

    Граница между предметно-ориентированными языками и языками сценариев несколько размыта, но предметно-ориентированные языки часто не имеют низкоуровневых функций для доступа к файловой системе, межпроцессного управления и других функций, которые характеризуют полную -функции языков программирования, сценариев и т. д. Многие предметно-ориентированные языки компилируются не в байтовый код или исполняемый код, а в различные типы мультимедийных объектов: GraphViz экспортирует в PostScript, GIF, JPEG и т. Д., Где Csound компилируется в аудиофайлы, а предметно-ориентированный язык трассировки лучей, такой как POV, компилируется в графические файлы. Компьютерный язык, такой как SQL, представляет собой интересный случай: его можно рассматривать как предметно-ориентированный язык, поскольку он специфичен для определенного домена (в случае SQL, доступ к реляционным базам данных и управление ими) и часто вызывается из другое приложение, но SQL имеет больше ключевых слов и функций, чем многие языки сценариев, и часто рассматривается как самостоятельный язык, возможно, из-за преобладания манипуляций с базами данных в программировании и уровня мастерства, необходимого для того, чтобы быть экспертом в язык.

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

    Инструменты программирования

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

    В проектировании, управляемом моделями, можно найти множество примеров языков, ориентированных на предметную область, например, OCL, язык для украшения моделей утверждениями или QVT, предметно-ориентированный язык преобразования. Однако такие языки, как UML, обычно являются языками моделирования общего назначения.

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

    Темы о предметных языках

    Внешние и встроенные предметно-зависимые языки

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

    Шаблоны использования

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

    • Обработка с помощью автономных инструментов, вызываемых через прямую операцию пользователя, часто в командной строке или из Makefile (например,, grep для сопоставления регулярных выражений, sed, lex, yacc, набор инструментов GraphViz и т. д.)
    • предметно-зависимые языки, которые реализованы с использованием макросистем языка программирования и которые конвертируются или расширен до основного языка общего назначения во время компиляции или в реальном времени
    • встроенный предметно-ориентированный язык (eDSL ), реализован в виде библиотек, которые используют синтаксис своего основного языка общего назначения или его подмножества из них при добавлении предметно-ориентированных языковых элементов (типов данных, маршрутов прочие, методы, макросы и т. д.). (например, jQuery, React, Embedded SQL, LINQ )
    • Domain-specific languages, которые вызываются (во время выполнения) из программ, написанных в общем целевые языки, такие как C или Perl, для выполнения определенной функции, часто возвращая результаты операции на «главный» язык программирования для дальнейшей обработки; как правило, интерпретатор или виртуальная машина для предметно-ориентированного языка встроена в хост-приложение (например, строки формата, a)
    • Доменные языки, которые встроены в пользовательские приложения (например, макрос языков в электронных таблицах) и которые (1) используются для выполнения кода, написанного пользователями приложения, (2) динамически генерируемого приложением, или (3) и того, и другого.

    Многие предметно-ориентированные языки могут использоваться в более чем одним способом. Код DSL, встроенный в основной язык, может иметь особую поддержку синтаксиса, например регулярные выражения в sed, AWK, Perl или JavaScript, или может передаваться в виде строк.

    Design goa ls

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

    предметно-ориентированные языки имеют важные цели проектирования, которые контрастируют с целями языков общего назначения:

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

    Идиомы

    В программировании идиомы — это методы, навязанные программистами для решения общих задач разработки, например:

    • Убедитесь, что данные сохранены до закрытия окна.
    • Редактируйте код всякий раз, когда параметры командной строки меняются, потому что они влияют на поведение программы.

    Языки программирования общего назначения редко поддерживают такие идиомы, но предметно-ориентированные языки могут их описывать, например:

    • Сценарий может автоматически сохранять данные.
    • Язык, зависящий от предметной области, может параметризовать ввод командной строки.

    Примеры

    Примеры языков, зависящих от предметной области, включают HTML, Logo для рисования карандашом, Verilog и VHDL описание оборудования языки iption, MATLAB и GNU Octave для матричного программирования, Mathematica, Maple и Maxima для символьная математика, язык спецификации и описания для реактивных и распределенных систем, электронные таблицы формулы и макросы, SQL для запросов к реляционной базе данных, YACC грамматики для создания синтаксических анализаторов, регулярных выражений для определения лексеров, Generic Eclipse Modeling System для создания языки диаграмм, Csound для синтеза звука и музыки, а также языки ввода GraphViz и GrGen, программные пакеты, используемые для макета графа и переписывание графов.

    язык GameMaker

    Язык сценариев GML, используемый GameMaker Studio, представляет собой предметно-ориентированный язык, ориентированный на начинающих программистов и позволяющий легко изучать программирование. Хотя этот язык представляет собой смесь нескольких языков, включая Delphi, C ++ и BASIC, отсутствуют структуры, типы данных и другие функции полноценный язык программирования. Многие из встроенных функций изолированы для облегчения переносимости. Язык в первую очередь служит для того, чтобы каждый мог легко освоить язык и разработать игру.

    Сценарии оболочки Unix

    Unix сценарии оболочки дают хороший пример предметно-ориентированного языка для организации данных. Они могут манипулировать данными в файлах или пользовательским вводом разными способами. Абстракции и нотации домена включают потоки (такие как stdin и stdout) и операции с потоками (такие как перенаправление и конвейер). Эти абстракции в совокупности образуют надежный язык для описания потока и организации данных.

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

    Эти задачи состоят из простого потока управления и строки механизмы манипулирования, которые охватывают множество общих применений, таких как поиск и замена string в файлах или подсчет вхождений строк (частотный подсчет).

    Несмотря на то, что языки сценариев Unix завершены по Тьюрингу, они отличаются от языков общего назначения.

    На практике языки сценариев используются для объединения небольших Инструменты Unix, такие как grep, ls, sort или wc.

    язык сценариев ColdFusion Markup Language

    ColdFusion, являются еще одним примером предметно-ориентированного языка для веб-сайтов, управляемых данными. Этот язык сценариев используется для объединения языков и служб, таких как Java,.NET, C ++, SMS, электронная почта, почтовые серверы, http, ftp, обмен, службы каталогов и файловые системы для использования на веб-сайтах.

    Язык разметки ColdFusion (CFML) включает набор тегов, которые можно использовать на страницах ColdFusion для взаимодействия с источниками данных, управления данными и отображения вывода. Синтаксис тега CFML аналогичен синтаксису элемента HTML.

    Erlang OTP

    Открытая телекоммуникационная платформа Erlang изначально была разработана для использования внутри Ericsson в качестве предметно-ориентированного языка. Сам язык предлагает платформу библиотек для создания конечных автоматов, общих серверов и менеджеров событий, которые позволяют быстро развертывать приложения или поддерживать библиотеки, которые, как показали отраслевые тесты, превосходят другие языки, предназначенные для смешанного набора доменов., например C и C ++. Язык теперь официально является открытым исходным кодом и может быть загружен с их веб-сайта.

    FilterMeister

    FilterMeister — это среда программирования с языком программирования, основанным на C, для конкретной цели создания Photoshop -совместимых надстроек фильтров обработки изображений. ; FilterMeister работает как плагин Photoshop и может загружать и выполнять сценарии или компилировать и экспортировать их как независимые плагины. Хотя язык FilterMeister воспроизводит значительную часть языка C и библиотеки функций, он содержит только те функции, которые могут использоваться в контексте плагинов Photoshop, и добавляет ряд конкретных функций, полезных только в этой конкретной области.

    Шаблоны MediaWiki

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

    Разработка программного обеспечения использует

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

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

    Более новой разработкой является языково-ориентированное программирование, интегрированная методология разработки программного обеспечения , основанная в основном на создании, оптимизации и использовании языков, специфичных для предметной области.

    Метакомпиляторы

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

    Помимо синтаксического анализа предметно-ориентированных языков, метакомпиляторы полезны для создания широкого спектра инструментов разработки и анализа программного обеспечения. Методология мета-компилятора часто встречается в системах преобразования программ.

    Метакомпиляторы, которые сыграли значительную роль как в информатике, так и в компьютерной индустрии, включают Meta-II и его потомок TreeMeta.

    Unreal Engine до версии 4 и другие игры

    Unreal и Unreal Tournament представили язык под названием UnrealScript. Это позволило быстро разработать модификации по сравнению с конкурентом Quake (с использованием движка Id Tech 2 ). В движке Id Tech использовался стандартный код C, что означало, что C нужно было изучить и правильно применить, в то время как UnrealScript был оптимизирован для простоты использования и эффективности. Точно так же при разработке более поздних игр были введены собственные специфические языки, еще одним распространенным примером является Lua для написания сценариев.

    Механизмы правил для автоматизации политик

    Различное Механизмы бизнес-правил были разработаны для автоматизации политик и бизнес-правил, используемых как в правительстве, так и в частном секторе. ILOG, Oracle Policy Automation, Drools и другие обеспечивают поддержку DSL, предназначенных для поддержки различных проблемных областей. доходит до определения интерфейса для использования нескольких DSL в наборе правил.

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

    Языки статистического моделирования

    Разработчики статистического моделирования разработали предметно-ориентированные языки, такие как R (реализация языка S ), Ошибки, Jags и Stan. Эти языки предоставляют синтаксис для описания байесовской модели и создают метод ее решения с помощью моделирования.

    Создание модели и сервисов для нескольких языков программирования

    Создание обработки объектов и сервисов на основе языка описания интерфейса для предметно-ориентированного языка, такого как JavaScript для веб-приложений, HTML для документации, C ++ для высокопроизводительного кода и т. Д. Это делается с помощью кросс-языковых платформ, таких как Apache Thrift или Google Protocol Buffers.

    Gherkin

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

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *