?

Log in

No account? Create an account

Previous Entry Share Next Entry
Редактирование кода программы как дерево, а не текст
mkizub
Чтобы понять, чем SOP/SymADE отличается от MPS или IP надо вначале разобраться, что у них общего.

Прежде всего, все эти средства разработки программ предполагают переход от представления программы в виде текста к представлению (редактированию и хранению) в виде дерева. В таком подходе нет ничего нового, именно эту идею использует Lisp, что в конечном итоге позволяет ему быть столь мощным средством мета-программирования. Например, возьмём такой простой кусочек кода как выражение 2 * (a + 1). В Lisp-е он будет записан как (* 2 (+ a 1)) - согласно простым правилам: каждый элемент кода кроме простейших атомом отделяется скобками, первым элементов в скобках является "символ" операции, и затем аргументы той операции. В данном случае у нас есть операция умножения (символ *), с аргументами 2 и (+ a 1), то есть второй элемент это операция сложения (символ +) над аргументами a и 1. Это выражение разбирается Lisp-ом и хранится внутри него в виде дерева. Такой синтаксис, конечно, менее удобен, чем привычная нам со школьной скамьи запись. Но он позволяет одну важную вещь - в любом месте программы мы можем использовать любой код (значение которого определяется символом, первым элементом в списке). Что в свою очередь является фундаментом (синтаксическим) для мета-программирования.


Одна из идей лежащих в основе SymADE/MPS/IP - это хранение и манипуляция кодом программы так-же как в Lisp-е, в виде дерева. Но при этом мы можем определять удобный для нас "синтаксис", только в данном случае - это просто способ отображения кода на экране. Тот-же код, который в Lisp-е мы вынуждены хранить и редактировать как (* 2 (+ a 1)), то есть списки/дерево структурированне лишь скобками, в SymADE/MPS/IP может быть отображён привычным нам образом, как  2 * (a + 1). При этом внутри программы мы всё так-же имеем дерево, но за счёт специально задаваемых правил отображения, мы можем показать это дерево в том виде, который нам удобен.


Зачем это нужно? Для этого есть несколько причин. Первая - мы можем отображать нашу программу так, как нам удобно, не ограничивая себя проблемами однозначности синтаксиса, да и сам парсер для разбора текста (для преобразования его во внутреннее представление компилятора) нам не нужен. Второе, это возможность отображатьодин и тот-же код по разному для разных программистов, в зависимости от их предпочтений - начиная с правил оформления кода (отступы, пробелы, переносы строк), и кончая возможностью задать совершенно другой "синтаксис" отображения (одни предпочитают С-подобный синтаксис, другие Pascal-евидный и так далее). Третье, это возможность отображать программу с разным уровнем детализации. Скажем, адепты функционального программирования очень гордятся автоматическим выводом типов функций и переменных, что позволяет им зачастую не указывать эти типы, за счёт чего программа получается намного более компактной и удобной для чтения. Но автоматический вывод типов - ничто, по сравнению с возможностью произвольно задавать уровень детализации отображения программы. Вместо автоматического вывода типов мы можем просто задать нашему IDE не отображать типы - и получить более компактное отображение кода. Мы даже можем задать совершенно "высокоуровневое" отображение кода, чтоб он выглядел как UML диаграммы. При этом мы не меняем сам код, и не генерируем из UML код на другом языке программирования - мы просто таким способом отображаем нашу программу. Четвёртая причина - это полноценная интеграция вновь определённых понятий в IDE. То есть при редактировании кода мы получаем авто-дополнение кода при редактировании, быстрый переход в место определения символов или нахождение всех мест где они используются, мы можем задавать рефакторинг (преобразование узлов дерева одного типа в узлы другого типа) и так далее.


Но есть и ещё одна, на мой взгляд, самая главная причина, по которой имеет смысл отказаться от текстового (Lisp-подобного) представления в пользу кода хранимого в виде дерева с произвольным отображением. Ведь в чём главная сила Lisp-а, ради чего он жертвует выразительностью синтаксиса? Ради возможностей предоставяемых мета-программированием! Возможности писать код который будет генерировать код для вашей программы, проверять правильность кода вашей программы и так далее. Так вот, мета-программирование становится особенно нужным, когда размер кода проекта становится настолько большим, что его уже невозможно поддерживать и развивать используя ограниченный набор понятий языка общего назначения. Использование мета-программирования позволило-бы уменьшить код таких больших проектов в десятки раз, вновь сделав его достаточно простым и понятным, для дальнейшего развития. Но увы, Lisp предоставляет возможности мета-программирования только для программ написанных на Lisp-е. Если ваш проект был написан на С, С++, C#, Pascal, Java и пр. - ничем вам мета-программирование Lisp-а не поможет. Так вот, IDE подобное SymADE/MPS/IP - может вам помочь в этом случае. Для этого необходимо определить в этой среде набор понятий подобный набору понятий в C, C++, C#, Pascal, Java и пр., а затем экспортировать старый проект в данную среду. После этого программист будет видеть код в привычном ему виде, и может продолжать редактировать его как если-бы это был код на его старом языке программирования, но плюс к этому - он получает возможность определять и использовать новые понятия, специфические для его проекта.


Возможность интеграции в IDE вновь созданных языков программирования или расширения существующих языков программирования, а так-же возможность плавного перехода к использованию мета-программирования - это столь важные преимущества SymADE/MPS/IP-подобных IDE, которые позволят в самом ближайшем времени (в течении нескольких лет) стать им одними из доминирующих средств разработки программ. Особенно в областях, где важно применение DSL (Domain-specific language), и при разработке крупных программных проектов.


Но эти преимущества - это только начало. Точнее, MPS от JetBrains и IP от Intentional Software только приближаются к реализации этих возможностей, и это практически все идеи, заложенные в них. Но в SOP заложено намного больше. Несмотря на технологически подобную реализацию (внутреннее представление программы в виде дерева, хранение его в виде дерева, и произвольное отображение этого дерева, изначальная интеграция новых понятий и языков в IDE), фундамент у SOP намного глубже, и в перспективе позволит пойти намного дальше, чем MPS и IP.


В следующем посте я наконец дойду до различий между SOP/SymADE и MPS, IP.

Tags: , , ,


  • 1
еще можно гетерогенным программированием проблему решить, совмещая несколько языков в проекте, например я пробовал Prolog+Java, вроде потенциал есть

Да, совмещение нескольких языков в одном проекте - это очень хороший способ писать сложные программы. Но! только если различные части (написанные на разных языках) хорошо изолированы друг от друга.
Например, то-же совмещение явы с прологом. Много лет назад у меня встала такая задача. У меня было несколько под-задач, где основная идея пролога (доказательство или поиск решения при сложных правилах, с возможным бэктрэкингом) мне бы очень пригодилась. Это были такие части как а) разбор кода при наличии новых (определяемых программистов) операторов (с приоритетом и ассоциативностью), и б) резолвинг имён (то есть дано имя 'foo' и надо найти декларацию - переменной, метода, типа и так далее). Для разбора кода с определяемыми операторами мне нужно было перебрать все варианты (и доказать, что это единственный возможный способ разбора выражения). А для резолвинга имён правила поиска были уж очень сложными. Без поиска решений с бэктрекингом было очень сложно писать для них код.
Так вот, конечно я первым делом взглянул на реализации пролога (для JVM). Но увы, ни одна из них мне не подошла. По той причине, что данные (факты и правила) содержались в самой программе. Чтоб использовать внешний пролог мне нужно было-бы экспортировать все данные из своей программы в прологовское представление, что само по себе достаточно трудоёмко. И плюс, эти данные постоянно могут меняться (удалили переменную - беги в пролог и сообщай, что такой переменной больше нет). И мне пришлось написать свою пролог-подобную систему. Просто из-за того, что эти две под-системы (компилятор и система поиска решений) были очень тесно связаны.

Другой пример. Пишут компьютерные игры. У них есть несколько отдельных частей - движок игры (то, что рисует), модель мира (дизайн уровней, данные для движка), сценарий игры. Они могут быть достаточно независимыми, то есть графический движок совершенно не зависит от дизайна уровня и сценария игры и так далее. Тогда движок пишут на С/C++, мир моделируют в отдельном редакторе (вообще-то это тоже такой DSL, без текстового представления), сценарий кодируют на скриптовом языке. Есть несколько точек соприкосновения этих независимых частей (скажем, движок определяет, что мы попали в пределённую точку на карте мира и вызывает для неё интерпретатор скриптовой функции), но их не много. Такой вариант - вполне жизнеспособный для гетерогенного программирования. Завели по языку на каждую часть проекта, и работаем.
Но эти части могут оказаться и очень зависимыми, сильно связанными друг с другом. Например, динамическое изменение мира вызываемое скриптами (вызываемыми по сценарию игры или в зависимости от действий пользователя). В этом случае нам надо почти всё экспортировать/импортировать методов между движком и скриптами. Нам надо менять модель мира из скриптов. Нам надо в модели мира укзывать вызываемые скрипты. И так далее. Работа по связыванию всего этого вместе может оказаться больше, чем если писать всё это на одном языке... Как-то я пробовал к 3D игрушке прикрутить скрипты на TCL... прикрутил, конечно, но либо функциональность у скриптов получалась минимальная, либо почти всю логику программы пришлось-бы писать на TCL, а это жуткий тормоз для реал-таймовой 3D игры.

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

да, языки могут быть разными, но желательно работать в одной среде, тут согласен..
я то явовский пролог опенсорсный подключал + соединил его с ява-частью через аннотации, которые очень удобны для метапрограммирования и вроде ничего так заработало, удобно если ява функция превращается в предикат при помощи аннотации
(Reply) (Thread)

Кстати, какая это была реализация, кто умеет подхватывать явовские методы по аннотации?

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

Очень интересно. Вообще идея решения особо острых проблем программирования, "летающая в воздухе", похоже начинает приобретать конкретные формы, что говорит о ее зрелости, что не может не радовать.
Хотелось бы узнать поконкретнее, а какие еще вопросы рассмотрены в контексте SOP кроме древовидного представления программы? Попробую пояснить, если ошибаюсь, пожалуйста поправьте. В статье даются разъяснения, что хранение программы в виде дерева, позволит показать ее в дальнейшем, в удобной для понимания того или иного аспекта форме. Так? Ну например графически показать диаграмму классов (если ООП) или модулей. Здорово, но вот, что более интересно, как например отобразить thread модель того или иного участка или всего проекта? Можно это сделать? Или, скажем, семантика суть конечный автомат, сможем ли мы выудить граф состояний? Реальный примеры представлений очень помогут пониманию идеи.
Еще раз спасибо.

Как отобразить thread модель? Для существующих онтологий (C++, Java) - никак. Почему?
В ООП языках есть понятие класса. Вы явно указываете, какие классы у вас есть, вы явно указываете, какой метод принадлежит какому классу и так далее. Используя эту информацию вы можете нарисовать диаграмму классов.
А вот понятия треда в этих языках отсутствует. Оно есть у нас с голове, мы знаем, что класс, который extends Thread - это класс для треда. А Java как язык, компилятор явы - этого не знает. Для него это просто такой класс, который наследует другой класс.

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

В SOP можно создавать свои понятия, новые семантические понятия, вроде треда. Создавать и использовать (для анализа, и пр.). Вы можете взять существующую онтологию (вроде онтологии языка Java) и добавить в него это семантическое понятие. Или создать новую онтологию (новый язык) в котором понятие треда будет изначально существовать. Если вы берёте за основу существующую онтологию, вы можете попробовать вывести информацию о использовании кода в различных тредах из имеющегося кода. То есть напишите отдельный "плагин" к компилятору, который будет анализировать код, будет знать о классе Thread, будет знать о библиотеках, которые создают и управляют пулом тредов, о том как в эти треды попадает код - и анализируя control flow вывести какие методы и участки кода в каких тредах используются. А можете потребовать прямые аннотации, прямые объявления тредов и участков кода.

По поводу примера представления. Ну, а что вы хотите от вашей тредовой модели программы? Какую информацию вы хотите поместить в понятие "тред"? Вот это и будет ваше представление. Если вы просто хотите иметь именованные треды, то в SOP вы создаёте семантическое понятие треда и у него будет аттрибут "имя". Если хотите ещё указывать приоритет - создаёте аттрибут "приоритет". В общем, для построения модели этого достаточно - потом в коде расставляете аннотации к методам, вроде "выполняется только в тредах (список)", и можно отображать. Для компиляции вы, видимо, должны ещё задать способ преобразования вашего понятия в понятия целевой платформы. Если это ява, то генерировать код приблизительно "class $Имя extends Thread {...}".

Что до графа состояний, ответ тот-же. SOP не будет выуживать граф состояний. SOP (SymADE как реализация этой парадигмы) просто позволяет определить новые понятия. Вы определяете понятие "состояние", понятие "граф состояний" (с соответствующими атрибутами). А потом либо задаёте эти состояния и граф состояний вручную (это и будет код вашей программы), либо пишете плагин к компилятору (IDE) который из вашего языка (из других понятий) будет выводить этот граф состояний (автоматически или полу-автоматически и так далее). При этом вы так-же можете задавать способ(ы) отображения этого графа, вы получаете возможность редактировать этот граф и эти состояния в IDE (включая автокомплит, copy&paste, drag'n'drop, и пр.). IDE вам подсветит ошибки (вашим же плагином обнаруженные), даст возможность рефакторинга (вашим же кодом реализуемые).

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

  • 1