пятница, 22 мая 2009 г.

Неудачная попытка избавиться от многословности в SObjectizer

На RSDN ввязался в дискуссию по поводу параллельного программирования и вставил там свои пять копеек на счет SObjectizer-а. После чего возник спор о том, является ли программирование с использованием SObjectizer кошмаром или нет (вот его начало). Резюмируя, суть претензий к SObjectizer можно свести к двум пунктам:

  1. SObjectizer заставляет разработчика сводить все сценарии работы агентов к явному конечному автомату. Что плохо для сценариев, в которых агенты должны делать синхронные запросы друг к другу. Т.е. послал агент A агенту B какой-то запрос и тут же “заснул” в ожидании ответа. Когда ответ пришел, его разбудили и он продолжил работу с той же точки. Причем все это выполняется в рамках одной функции агента A, с сохранением промежуточного состояния агента на стеке. Это модель взаимодействия легких процессов в Erlang. Так же она, насколько я понимаю, является обычной для языков со встроенной поддержкой продолжений (continuations). В SObjectizer-4 такая схема работы невозможна. В SObjectizer-4 агент A должен будет отослать сообщение агенту B сообщение и завершить обработку своего текущего события. Когда агент B пришлет ответ, у агента A сработает другое событие. Это означает, что свое промежуточное состояние агент A должен будет хранить не на стеке, а в своих атрибутах.
  2. SObjectizer-4 слишком многословен. По словам моего оппонента, многословность является следствием пункта номер 1.

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

А вот пункт второй действительно является болезненной темой. Во-первых, самому хотелось бы писать меньше кода при работе с агентами (хотя тут интересный момент есть: в более-менее серьезных агентах объем SObjectizer-овских описаний занимает очень небольшой объем по сравнению с прикладным кодом). Во-вторых, мне кажется, что объем кода в простых примерах (типа Hello, World) создает незаслуженное отрицательное впечатление о SObjectizer у новичков.

Поэтому устранение многословности SObjectizer является одной из главных целей разработки SObjectizer-5. И я вижу пока всего два взаимоисключающих пути достижения этой цели. Первый путь – это использование внешних DSL. По аналогии с тем, как используются IDL для описания интерфейсов, ASN для описания структур данных и пр. Лично мне этот путь кажется наиболее многообещающим. Но он встречает серьезное неприятие у других заинтересованных в SObjectizer разработчиков. Поэтому рассматривается и второй путь – использование только средств C++.

Как раз в попытке придерживаться второго пути пару месяцев назад у меня возникли мысли о том, как же описание агента могло бы выглядеть в SObjectizer-5. С использованием только простых возможностей C++ (как у некоторых есть предубеждения против внешних DSL, так же у меня есть предубеждение против навороченных шаблонных конструкций). Как мне казалось до вчерашнего дня, идеи были достаточно привлекательными.

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

Никакого выигрыша. Более того, мне кажется, что в новом варианте дублирования кода даже больше, чем в существующем.

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

Значит, нужно что-то править в консерватории. И я склонен еще более серьезно рассматривать варианты с внешними DSL. Не важно, будет ли этот DSL базироваться на Ruby или же это будет специализированный синтаксис и C++ный транслятор. Но нужно, чтобы описание агента было максимально простым и компактным. А весь необходимый оберточный C++ный код генерировался бы автоматически, без вмешательства со стороны программиста.

В этой связи вопрос к читателям (которым хватило интереса и терпения дочитать до этого места): если у вас есть предубеждения против использования внешних DSL (не важно, IDL, ASN, описаний для Google Protobuf, WSDL и пр.), то поделитесь, пожалуйста причинами возникновения ваших предубеждений.

9 комментариев:

Dmitry Vyukov комментирует...

Re: SObjectizer заставляет разработчика сводить все сценарии работы агентов к явному конечному автомату....

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

eao197 комментирует...

Да, так и есть. И это очень удобный прием.

Но в контексте тамошнего спора он не многим лучше сохранения состояния в полях агента. Ведь главное, что не на стеке с автоматической передачей контекста другому файберу.

Dmitry Vyukov комментирует...

А по поводу многословности мне всё же сильно импонирует вот такой вариант:
struct my_agent : public agent[my_agent]
{
void on_event(event[my_msg] ev)
{
std::cout << "my_msg::data=" << ev.msg->data << std::endl;
}
};
По-крайней мере будет замечательно смотреться в туториалах и хелло-ворлдах :)

eao197 комментирует...

Вот в пределе хотелось бы что-то подобное и получить. Только без CRTP :)

Dmitry Vyukov комментирует...

Пожалуйста: ;)

AGENT(my_agent)
{
ON_MSG(my_msg)
{
std::cout << "my_msg::data=" << msg->data << std::endl;
}
};

eao197 комментирует...

Не, тогда у разных IDE могут быть проблемы.
Да и declspec куда-то вставлять нужно.

Лучше бы все это автоматом генерировать :))

Dmitry Vyukov комментирует...

Автоматом хорошо генерить, например, функции сериализации. Пользователь знает, что он может вызывать serialize_person() и других связей с генерируемых кодом у него нет. А сама реализация может хоть по 10 раз на перегенерироваться, это пользователя не будет касаться.
А с агентами насколько я вижу это не прокатит. Как минимум имя типа агента и имена сообщений будут дублироваться в двух местах. Хочешь обработчик нового сообщения добавить - иди добавляй в 3 места - в DSL, в h-ник, в cpp-ник. Не помнишь в каком состоянии вызывается данный обработчик - будь добр сходить в отдельный файл (DSL) поглядеть. И т.д. Т.е. тут такой идилии как с сериализаией-то не получается. На примере IDL это уже видно, что приходится только в большем числе мест дублировать.
Не понимаю, почему ты так стремишься к DSL-ю... По сравнению с С++ с минимальным дублированием DSL неотвратимо только увеличит дублирование.
Самое прикольное так это вообще обработчики только в .cpp добавлять - добавил в одном месте функцию и уаля.

Dmitry Vyukov комментирует...

Можно конечно замутить что-то типа такого:
class my_agent
{
#include "mу_agent_autogen.h"
...
};
Тогда мы возвращаемся к нашим старым баранам - дублирование в 2 местах (DSL и cpp, а было h и cpp)

eao197 комментирует...

Да, вариант с двумя результатами генерации -- dsl->hpp и dsl->cpp уже хорошая штука. Т.е. мы что-то описываем в dsl-файле, а затем в своем hpp-файле делаем:

class my_agent ... {
#include "my_agent.dsl.hpp"
}

а в cpp-файле:

#include "my_agent.cpp.hpp"

Хороша она тем, что генерится может много кода. И останется минимальное дублирование в DSL и cpp. Боюсь, что если мы будем обходится только C++ шаблонами и макросами, то мы будем ограничены в своих возможностях.

А для того, чтобы объяснить, чем мне нравится DSL, нужно будет рассказать о том, что из себя представлял SO3.