catpad (catpad) wrote,
catpad
catpad

Category:

Декларативное программирование на С++ (для программистов)

Ну вот, собрался, наконец, написать о том, как я преобразовал С++ в 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. Ведь этот дебаггер, который я описал выше - это на самом деле семантический дебаггер: он работает не на уровне переменных и функций С++, а на уровне переменных (объектов) и действий той области, в которой его применяют. Он понимает их смысл.

Всё!

Subscribe

  • The Moth Quest

    Официально открываю новый квест — The Moth Quest AKA Нанюхавшаяся Моль. Участники взяли себе неделю отдыха от предыдущего…

  • Moth Quest

    Вы будете смеяться, но я сделал новый квест — Moth Quest. Под прошлым постом нашлось несколько желающих разгадывать в…

  • Восьмая глава

    Закончил восьмую главу «Хайдеггера и Самовара». Это глава о самых разных языках и о языке вообще, о поэзии, о каббале и о…

  • Post a new comment

    Error

    default userpic

    Your reply will be screened

    Your IP address will be recorded 

    When you submit the form an invisible reCAPTCHA check will be performed.
    You must follow the Privacy Policy and Google Terms of use.
  • 12 comments

  • The Moth Quest

    Официально открываю новый квест — The Moth Quest AKA Нанюхавшаяся Моль. Участники взяли себе неделю отдыха от предыдущего…

  • Moth Quest

    Вы будете смеяться, но я сделал новый квест — Moth Quest. Под прошлым постом нашлось несколько желающих разгадывать в…

  • Восьмая глава

    Закончил восьмую главу «Хайдеггера и Самовара». Это глава о самых разных языках и о языке вообще, о поэзии, о каббале и о…