catpad: (Default)
[personal profile] catpad

Ну вот, собрался, наконец, написать о том, как я преобразовал С++ в domain specific, declarative, functional, aspect-oriented, event-driven language :)



Disclaimer: Описанная здесь идея не имеет никакого отношения к моей работе и к технологии, которая используется в моей работе. Это моё личное "изобретение". Оно совершенно абстрактно и может применяться в любых областях.

Может, это я изобрёл такой велосипед, не знаю. В любом случае:

Возьмём обычную модель обработки непрерывного входного потока (input stream), который поделён на некие куски информации (messages), которые поделены на ещё более мелкие куски (fields), и т.д.




Обычно для каждого сообщения создаётся какой-нибудь класс, содержащий business logic. Этот класс принимает входные поля, объявляет всякие промежуточные переменные, вычисляет эти переменные, используя входные данные, проходит по разным веткам if-then-else и в конце концов вычисляет выходные объекты, которые посылает в какой-нибудь выходной поток (output stream).

Моей главной целью здесь было преобразовать программную модель таким образом, чтобы описывать не алгоритм выполнения задачи, а цели, которые поставлены перед программой (declarative programming). Эти цели будут "нанизаны" на "дерево" контроля, которое будет легко читаемым, и таким образом, должно быть сразу видно, что же делает эта программа.

Я подумал, что здесь можно сделать несколько вещей:

1) Вычленить из всех многочисленных внутренних разветвлений программы одно большое дерево (flow chart), которое бы содержало всю (во всяком случае - основную) логику программы.

2) Каждый узел и лист этого дерева описывался бы неким wrapper-объектом, который был бы связан с функцией-декларацией, вычисляющей этот объект. Такая функция не должна содержать никаких условий - то есть, она не описывает как вычислять значение, она просто говорит, что представляет собой это значение (в идеале такая функция содержит единственный оператор присвоения).
Wrapper-объекты бывают такими:
* объекты ввода
* объекты вывода
* операторы switch (которые распределяют контроль выполнения по разным веткам)
* внутренние переменные
* действия (то есть, объекты, "обёрнутые" вокруг методов).

3) Каждый из wrappers посылал бы events, таким образом сообщая всем заинтересованным сторонам о том, что с ним происходит (см. ниже).

4) Объекты ввода подписываются на events входного потока. Так они узнают о том, что у них появились значения, и всё "дерево вычислений" приходит в движение.

5) Выходной поток подписывается на events объектов вывода. Таким образом он сам распоряжается тем, что, когда и куда выводить. Сама главная программа ничего не знает о том, что происходит с объектами вывода.

Новая диаграмма получилась такой:





Главный "flow chart" создаётся с помощью операторов ввода и вывода, примерно так:

InputStream >> Field1
>> Field2
>> Field3
...

Switch1 >> Action1
>> Action2
>> Action3
>> Switch2;

Switch2 >> Action4
>> Action5;

Action1 >> Output1
>> Output2;

Action2 >> Output3
>> Output4
>> Output5;

OutputStream << Output1
<< Output2
<< Output3
...

Теперь посмотрим, что такое VariableWrapper (он также включает в себя InputWrapper и OutputWrapper):



VariableWrapper содержит внутри себя значение, которое может быть присвоено только один раз (no reassignments!), а также associated processing method, который вызывается только один раз, чтобы присвоить переменной её значение. Этот метод не должен иметь никаких побочных эффектов, обычно он состоит только из оператора =. Естественно, каждая переменная обычно зависит от нескольких других переменных: все их значения будут вычисляться lazily, по необходимости и только один раз.
Типичные методы вычисления переменных могут выглядеть так:

void defineA()
{
A = (B() + C())/2*D();
}

void defineB()
{
B = E()/F();
}

и так далее (содержание функий я сделал весьма нелепым, но это просто чтобы показать принцип).

Все действия с переменными производятся с помощью трёх операторов: = (присвоить), () (получить значение) и bool - узнать, присвоено ли переменной значение.

Если мы вдруг присвоили в функции defineA() значение другой переменной, то при попытке получить значение А, будет брошен "not assigned" exception, потому что значение А ещё не присвоено.
И наоборот, если в функции defineA() мы попытаемся присвоить значение ещё и переменной В, то при повторной попытке присвоить В будет брошен "reassignment" exception.
Всё это даёт нам некое подобие functional programming.

Таким образом, можно представить, что выполнение программы идёт как бы по двум большим ветвям: контроль "спускается" по явно заданному flow chart от верхнего уровня (когда определяется ввод) к нижнему (когда определяется вывод), в то время как данные "текут" по дереву зависимости переменных, неявно определённому самой структурой программы, от вводных полей, до объектов вывода:



Теперь посмотрим на ActionWrapper:



Здесь всё совсем просто, это не более чем завёрнутая в оболочку функция.

Вы, конечно, заметили, что VariableWrapper и ActionWrapper посылают события (events) при каждом удобном случае: Variable Assigned Event, Variable Accessed Event, Before Action Event, After Action Event.
Это даёт нам совершенно удивительные возможности.
Во-первых, Aspect-Oriented programming. Не нужно никаких специальных процессоров и препроцессоров. Если мы, например, хотим писать в logfile перед каждым выполнением (или после) определённой функции - нет ничего проще: достаточно только подписаться на events этой функции (это может сделать какой-нибудь LogWriter) и дело сделано. Заметьте, что основная программа абсолютно ничего не знает ни о каком логфайле или других подписчиках - код остаётся нетронутым. То же самое можно проделать с каждой переменной, вводным параметром или выводом программы, код опять же остаётся нетронутым. Любое "ортогональное" основной программе действие можно производить "за кулисами", не трогая при этом саму программу.

Во-вторых, можно выводить debug information, следить за разными проходами по дереву контроля, записывая таким образом различные test cases, и т.д. Программа может создавать свою собственную документацию прямо во время выполнения, и эта документация всегда будет правильной. Можно даже сделать специальный debugger (что я и сделал), который будет останавливаться на любых заданных breakpoints с любыми условиями (такая-то переменная получила такое-то значение). Достигается это за счёт того, что посылка events - это на самом деле function call, который получает контроль. Мой дебаггер, например, это GUI аппликация, которая работает на PC и контролирует программу, бегущую на Юниксе, за счёт того, что его ассистент на Юниксе получает контроль в event handler и держит его столько, сколько надо пользователю). Кроме того, мой дебаггер умеет рисовать красивое дерево контроля - как уже стало понятно, здесь всё готово для того, чтобы такую диаграмму было легко и удобно создавать, ведь каждый объект знает о своих предках и потомках.
Опять же - сама программа ничего не знает ни о каком дебаггере, и ни одной строчки кода менять в ней не нужно!

Почему я называю всё это DSL (Domain Specific Language), кроме всего прочего ?
Всё на самом деле зависит от того, какой смысл придать Wrapper Objects. Назовите их так, чтобы они наиболее близко отражали ту область, с которой работает ваша среда - и можете программировать уже не на С++, а на своём собственном DSL. Ведь этот дебаггер, который я описал выше - это на самом деле семантический дебаггер: он работает не на уровне переменных и функций С++, а на уровне переменных (объектов) и действий той области, в которой его применяют. Он понимает их смысл.

Всё!

Date: 2010-07-16 10:32 am (UTC)
From: [identity profile] cmm.livejournal.com
Может, это я изобрёл такой велосипед, не знаю.

велосипед "известен" (с разных углов) как "dataflow programming", "functional reactive programming", и возможно как-то ещё.

сильно ли многословно на выходит на C++?

Date: 2010-07-16 11:11 am (UTC)
From: [identity profile] catpad.livejournal.com
>> велосипед "известен" (с разных углов) как "dataflow programming", "functional reactive programming", и возможно как-то ещё.

Это не совсем верно сформулировано: я просто использую эти понятия в своей модели наравне с элементами функционального и декларативного программирования. То есть, следовало бы начать запись так: "как я преобразовал С++ в domain specific, declarative, functional, aspect-oriented, event-driven, dataflow, reactive programming language".

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

Date: 2010-07-16 04:57 pm (UTC)
From: [identity profile] yatur.livejournal.com
Вот пример бы какой. А вообще сильно напоминает, скажем, BizTalk или Windows Workflow Foundation. Или, наверное, BPEL какой-нибудь, но я в нем не силен.

Date: 2010-07-16 05:02 pm (UTC)
From: [identity profile] yatur.livejournal.com
В смысле, если бы был пример какой-нибудь маленькой почти практической задачи, от начала и до конца, понять про что это все было бы куда проще. А так надо додумывать, как именно из void defineB() { B = E()/F(); } получается законченная программа.

Date: 2010-07-16 09:52 pm (UTC)
From: [identity profile] catpad.livejournal.com
Я могу прислать небольшой пример HelloWorld вам по почте.

Date: 2010-07-17 08:44 am (UTC)
From: [identity profile] towndwarf.livejournal.com
и мне, и мне :)

Как мне кажется, я пользовался(написал) чем-то похожим в одном из проектов.
Т.е. были listeners на входах и выходах, все части программы/программ 'бросали' и 'ловили' events, единственно что,
- bottleneck'ом оказался модуль обработки и распределения events (кое что пришлось даже на ASM переписать...я тогда его ещё помнил),
- формат events был определён заранее (использовалась CORBA)
- программа могла 'подписаться' на event но не вытягивать его из очереди
- необработанные events через определённое время (в самом event'е поле) самоуничтожались

Посему - интересно посмотреть на то, как ты это сделал у себя :)

Date: 2010-07-18 10:11 am (UTC)
From: [identity profile] catpad.livejournal.com
Пришлю.
Ну, events у меня это вторичная вещь. Вообще, я бы сказал, что главное в этой модели - стиль. Я так это и подаю, когда объясняю: программа, имитирующая спецификацию входного потока.

Date: 2010-07-18 01:22 pm (UTC)
From: [identity profile] towndwarf.livejournal.com
Spasib :)
У меня events вроде как тоже были вторичной вещью, пока не оказалось, что нужно обрабатывать все потоки в "реальном времени" :)

Date: 2010-07-16 06:52 pm (UTC)
From: [identity profile] gianthare.livejournal.com
А я думал страшные темплейты будут

Date: 2010-07-16 10:05 pm (UTC)
From: [identity profile] catpad.livejournal.com
Один раз я уже сделал framework со страшными темплейтами. Они были настолько страшные, что никто не могут понять, как же это всё работает (включая меня). Достаточно сказать, что вложенность темплейтов там иногда достигала двадцати, и данные перетекали от типа к типу каким-то typedef-ным образом.
По сей день существует одна программа, которая этим пользуется, и я всё думаю, как бы найти время её переделать, потому что поддерживать её невозможно.

С тех пор я решил больше страшными темплейтами не пользоваться. В этой библиотеке они всё равно есть, конечно, но всё очень просто и по делу.

Date: 2010-07-16 07:14 pm (UTC)
From: [identity profile] barabek.livejournal.com
Ага, хорошо.. Спасибо, Миша!

Описанные тобой принципы и их воплощение я понял,
хотелось бы посмотреть на практическое применение.

Как, например, будет выглядеть написанная при помощи твоей framework
программка для переставления слов в предложении?
Given an array of characters which form a sentence of words, give an
efficient algorithm to reverse the order of the words (not characters) in it.

Если у тебя сейчас нет времени для написания ее,
можешь ли показать код примерно такого же уровня:
т.е. компактно, но не на уровне "Hello World"

Date: 2010-07-16 09:58 pm (UTC)
From: [identity profile] catpad.livejournal.com
Алик, я пришлю тебе работающий пример после выходных.
Кстати да, было бы интересно применить этот framework для какой-то простой задачи из совсем другой области, надо будет подумать.
Page generated Feb. 6th, 2026 07:08 am
Powered by Dreamwidth Studios