В продолжении предыдущей публикации по теме Domain Driven Design, где Николай Гребнёв последовательно свёл тему проектирования при помощи DDD к необходимости использования языка предметной области, — в данной публикации будет обсуждаться практика проектирования и разработки как самих языков, так и прогрммирование на них (опыт компании JetBrains).
Доклад smax Максима Мазина с прошлогодней конференции архитекторов ПО Application Developers Days
Видео доклада:
Скачать
ftp.linux.kiev.ua/pub/conference/peers/addconf/2011/1a1-language-oriented-programming-mazin.avs.avi
Презентация
docs.google.com/present/view?id=dccwwvbq_729dxjj82gc
Текстовка доклада (выполнена Belonesox)
Сегодня у меня первый доклад, рассказывать я будут про языковый тему (???) в программировании.
Меня зовут Максим Мазин, я работаю в компании JetBrains инженером в проекте YouTrack.
YouTrack — это багтрекер, и самое увлекательное в YouTrack то, что он написан с помощью
Language Record программирования.
Что это такое? Это такая концепция, которая полагает, что при написании программ, первым делом вы создаете
специальный предметно-ориентированный язык, а затем, на этом предметно-ориентированном языке, вы начинаете уже писать собственно код программы.
Для чего это? Какой от этого прок, и что мешает делать так всем — об этом я и буду сегодня рассказывать.
Смотрите — вот существуют универсальные языки программирования. При программировании при помощи языка Java,
PHP или C# возникает проблема — у вас при программировании возникает огромное количество bullet plate (??? 01:15) -кода, т.е. такого кода, который будет повторяться из раза в раз, но не потому, что он делает какую-то содержательную работу, а просто для того, чтобы компилятор мог этот код скомпилировать.
Писать скобочки круглые, точки, … ну и так далее.
В случае, когда вы пользуетесь каким-то фреймворком, то у вас появляется еще дополнительная боль от фреймворка.
Вам нужно инициализировать библиотеки, открывать окна, устанавливать какие-то свойства, в зависимости от того, что у вас за библиотека.
В принципе, во многих современных языках программирования, ну например, С#, существует практика добавлять в язык предметно-ориентированные конструкции.
В языке Java существует конструкция synchronized, которая не является какой-то конструкцией общего назначения, является специальной, предметно-ориентированной конструкцией, предназначенной для параллельного программирования.
Точно так же, в языке C++, существует перегрузка операторов. Вот, сравните.
В нормальной ситуации, если бы у вас не было конструкции synchronized вам бы пришлось написать вот столько вот кода.
Блок «finally» и блок получения блокировки, это все вот bullet-plate код, который не интересно писать постоянно.
Вместо этого существует DSL-ная конструкция «synchronized», которая позволяет захватить блокировку и не беспокоится о том, что ее надо отпустить.
Точно также в языке C++ есть механизм перегрузки операторов, который позволяет вам сделать вид, как будто у вас определены специальные операции, например, над комплексными числами.
Это все очень хорошо, но как вы знаете, что в языке Java, начиная с пятой версии, появились read-write локи,
и это означает для нас, что нам снова нужно писать код вот такого стиля, как показано слева:
Т.е. надо блокировки захватывать, блокировки отпускать, … это все полностью нивелирует удобства.
Если бы можно было писать… самостоятельно расширять языки, нам бы удалось решить разные проблемы.
Например, те конструкции, языковые расширения, которые есть, в языке Java, они позволяют вам решать определенный набор задач. Но мы в жизни сталкиваемся и с другими задачами. Например, если мы пишем веб-аппликейшн, то нам нужно
работать с базами данных, писать веб-уровень, и так далее. Для этого DSLей нет, есть только фреймворк.
Точно также, если мы используем какую-нибудь концепцию вроде dependency injection, то нам тоже нужно для этого использовать фреймворки, и хуже того — использовать смеси языков, писать одновременно на Java, на XMLе, и все это не очень весело.
И главное — нам ничего с этим не поделать, потому что создание языкового расширения грозит нам разными рисками.
Какие проблемы возникают при создании DSL и почему бы при возникновении какой-нибудь потребности не бросаться сразу создавать DSL?
Самая главная проблема, с которой все сталкиваются все люди, которые пытаются заниматься созданием DSLей,
это совместимость языковых расширений.
Совместимость имеется в виду возможность использовать одновременно два языковых расширения, которые созданы независимыми разработчиками.
Вот например. Если у вас есть некоторые библиотеки и фреймворки, в нашем случае для Java, например
Hibernate, Spring или Joda Time, то
все они между собой совместимы, принципиально, на уровне компиляции.
Т.е. вы можете одновременно использовать в вашем проекте компоненты и Hibernate и Spring, и ничего вам не может помешать.
Но если у вас есть языковые надстройки, в виде каких-то макросов, или еще каких-нибудь техник, которые добавляют вам предметно-ориентированные конструкции в язык Java, то и эти надстройки созданы независимым образом, то все время существует риск, что эти надстройки будут с друг другом конфликтовать.
Вот например, такой очень простой пример.
Допустим у нас откуда-то появилось расширение, которое делает что-то полезное, и в частности поддерживает интерполяцию выражений в строках.
Можно написать такую конструкцию, и вместо «resultCount» будет подставлено значение этого выражения.
В тоже самое время, в друг к нам приходит другое языковое расширение, которое позволяет делать что-то полезное и в частности тоже делает интерполяцию строк. В Java же нет интерполяции строк, поэтому достаточно ожидаемо, что разные люди будут ее релизовывать в разных языковых расширениях.
И допустим, синтаксис слегка отличается. Тогда при совместном использовании языковых расширений A и B, у вас мгновенно возникает неустранимая неоднозначность, как следует интерпретировать вот этот вот buzz.
Данный пример он очень простой, но по факту, все, кто создает DSL на основе текстовой информации, так или иначе сталкиваются с проблемой, у них возможно нет уверенности в том, что расширения не будут противоречивы.
Другая проблема, которая мешает нам создавать DSLи. Допустим тем не менее, вопреки сложностями и проблемам с совместимостью разных расширений вы принимаете решение написать DSL.
Пишите какой-нибудь препроцессор, натравливаете этот препроцессор на вашу программу
вашими DSLными конструкциями, она компилирует и выдает какой-то код, байт-код например,
или какой-то исполняемый код.
Это все очень хорошо, но в наше время никто не пишет код, не используя IDE.
Так не делают просто, потому что без использования IDE продуктивность разработчика драматически снижается.
Поэтому в большинстве случаев, когда вы создадите такой DSL, будут ситуации, когда ваши разработчики, ваши коллеги,
предпочтут использовать просто язык Java в Idea, просто язык C# вместе с ReSharperом, нежели брать ваше поделие, и что-то с помощью вашего поделия писать, потому что нет поддержки IDE.
Под поддержкой IDE понимается все возможности, которые современные IDE оказывают разработчику. Т.е. это подсветка синтаксиса естественно, подсветка ошибок до компиляции, рефакторинг, интеграция с Version Control-ами, все остальное.
И да, можно отметить, что само по себе создание языка
это решенная математическая задача.
Если у вас есть пример для представления синтаксиса, вы сможете создать компилятор.
Но она, тем не менее, трудоемкая, даже если вы используете компилятор компиляторов.
И если вы погружаетесь в языково-ориентированное программирование очень глубоко, т.е. вы начинаете строить разные расширения, то у вас проблема совместимости выходит на новый уровень. Т.е. у вас не только совместимость на уровне грамматики, но также например, на уровне системы типов.
Т.е. если у вас есть два независимых расширения, то нужно, чтобы системы типов этих двух расширений, нормально отрабатывали и нормально приводились друг к другу.
Существуют очень много разных попыток сделать это, поддержать DSL в том или ином виде, тема очень горячая, тут наверное еще можно добавить Nemerle, и может быть еще OCaml, сейчас есть очень много разных средств, чтобы создавать DSLи.
В рамках Eclipsa существует XText-фреймворк, который позволяет написать грамматику и получить что-то вроде DSLя c поддержкой IDE.
У всех у них есть проблемы. Главная проблема вытекает из того, что они ориентированы на текстовые грамматики.
А раз они ориентированы на текстовые грамматики, то у них нет никаких шансов избежать проблемы совместимости конструкций.
Но к счастью, в нашей компании JetBrains, была создана среда MPS.
MPS означает meta-programming system, в основном предназначена для создания и расширения языков, причем такого расширения, что сразу после его создания появлялась поддержка IDE.
Я уже много раз сказал, что текстовые грамматики неизбежно приводят к проблемам. Поэтому естественным решением является работа напрямую с AST → абстрактным синтаксическим деревом, и заставлять программиста прямо, напрямую создавать абстрактное синтаксическое дерево.
Попытки такие ранее предпринимались, и как правило такие попытки сводятся к редактированию диаграмм.
Рисуется диаграмма, в некотором смысле… у меня ребенок программирует на свойствах в большей степени, вы рисуете диаграммы, у вас получаются картинки, далее эти картинки собираются, получается код.
Есть проблема — рисовать эти картинки неудобно — мышкой хватать, таскать, неудобно.
У нас, у программистов, есть привычка писать код вручную.
Поэтому в MPSе реализована концепция проекционных редакторов.
О чем речь? Вот у вас есть абстрактное синтаксическое дерево. Т.е. это то представление программы, которое получается, в любом случае получается, когда парсер распарсил программу.
В то же время, у вас есть его проекция, во что-то вроде текста.
Для каждого узла этого синтаксического дерева есть соответствующая ему ячейка.
Те из вас, кто знакомы с TeXом, должны примерно представлять, примерно похожие концепции.
Точно также, для каждого элемента у вас есть ячейка, ячейки объединяются в еще более крупные ячейки и так далее.
При этом воздействие на эти ячейки, когда вы пишете в эти ячейки, или каким-то образом на них воздействуете, то сразу же, мгновенно влияет на эту деревяшку.
При этом наша цель, как разработчиков MPS, сделать так, чтобы вы вообще не чувствовали, что вы работаете не с текстом, а с напрямую с деревяшкой. Здесь конечно проблема, но те из вас, кто пользуются IDEA, должны понимать, что когда вы редактируете текст в IDEA, Eclipse или еще где-то, вы тоже не редактируете текст — вы тайпаете что-то, что поглощается IDEA, и она это во что-то превращает.
В случае Eclipse это в большей степени редактирование текста, в случае IDEA — это еще меньше редактирования текста.
У такого проекционного редактора есть положительные стороны, и есть отрицательные стороны.
Положительные стороны понятны. У нас нет никаких текстовых грамматик, никакого текста, никаких проблем текстовых грамматик, никаких конфликтов, все совместимо, все очень хорошо.
Отрицательная сторона, состоит в том, что все-таки есть некоторое отличие от редактирования текста. В тоже самое время, (???) проблема. Дело в том, что привыкание… по нашему опыту, люди, которые начинают работать с MPSом, они привыкают к стилю программирования внутри MPSа, примерно в течении двух недель.
Т.е. в течении этих двух недель они могут выдавать какую-то положительную обратную связь, о том, что им неудобно, непривычно, чем-то отличается от программирования в текстовом редакторе.
Через две недели они привыкают и начинают работать с той же скоростью, как они работали ранее.
Я надеюсь, что если вы попробуете скачать, и начнете пользоваться, то мы получим больший feedback, и мы сможем сделать проекционный редактор еще более похожим на текстовый редактор.
Идея такая. При создании языков вы выписываете мета-модель языков, его абстрактный синтаксис, задаете систему типов, описываете конкретный синтаксис, и автоматически получаете на выходе язык, вместе с компилятором, плюс MPS-тест для работы с кодом на этом языке.
Внутри MPSа, вместе с MPSом поставляется ряд готовых расширений,
поскольку мы проекты пишем в конце-концов на Java, т.е. мы все генерим в джаву,
то у нас стек языков который есть предназначен в конце-концов для джавы.
Вместе с MPSом, внутри него, поставляются разные языковые расширения, для работы с замыканиями, с коллекциями… и так далее. И некоторое количество языков для работы с XML, тупо для работы. Т.е. вы можете вводить XML, и что самое главное — генерировать XML из своего кода.
Ну и еще есть некоторое количество языков, которые предназначены для создания языков.
MPS в плане создания языков вполне честно (??? 16:20)
Вместо демонстрации — выглядит как-то так ↑.
Я показывают не со своего компьютера, поэтому все, кого интересует демонстрация, приглашаю на стенд в коридоре.
Тут посмотрите, как просто взять и заDSLлить поддержку языковой конструкции.
Фактически, это все, что я хотел рассказать, осталось несколько важных вещей.
MSP подходит не только для создания языковых расширений, т.е. не только в Java можно добавить конструкций, которых в них не хватает, но также вы можете создавать какие-то собственные DSLи.
Вот например, Мартин Фаулер создал язык для описания своей финансовой отчетности.
Кроме того, с помощью MPSа мы написали YouTrack, такой большой багтрекер — большой, в смысле с большим количеством кода (сгеренированного). Но мой предыдущий опыт разработки подсказывает мне, что код содержательный,
который мы пишем и редактируем, этого кода достаточно мало.
Опять таки интересующимся, мы на выходе можем показать, как выглядит код в редакторе, сколько там таблиц.
Кода достаточно мало, из-за того, что уровень абстракции поднят достаточно высоко.
Для разработки YouTrack был создан набор языков, часть этих языков открыта, а часть — закрыта.
Мы их пока не отдаем, потому что они не достигли пока продажного качества.
Вот, приложение настоящее, живое.
У MPSа есть положительное свойство. Он «Apache 2.0»-лицензированный, абсолюно бесплатный, даже для коммерческого использования.
Скачать его можно здесь.
www.jetbrains.com/mps
Блог про MPS:
blogs.jetbrains.com/mps
Теперь если у вас есть какие-то вопросы, предлагаю вам их задать.
(18:46)
Автор: Polazhenko