| Author: | Евгений Охотников |
|---|---|
| Contact: | eao197 at intervale dot ru; eao197 at yahoo dot com |
| Date: | 2007.07.22 |
SObjectizer-4 не использовал внешних языков для описания классов агентов поскольку первая версия SObjectizer-4 создавалась в очень сжатые сроки и на разработку специализированного языка (DSL -- Domain Specific Language) просто не было времени. Вероятно, на тот момент это было правильное решение, которое позволило получить работающий SObjectizer и применить его в реальных проектах.
По прошествии пяти лет использования SObjectizer-4 возникает вопрос о то, каким будет SObjectizer-5. Возможно, следующие версии SObjectizer либо будут серьезно переделаны по сравнению с SObjectizer-4, либо вообще будут созданы для других языков программирования (D/C#/Java/Scala/Eiffel, конкретный язык еще не выбран). Поэтому есть смысл вернуться к идее внешнего DSL для SObjectizer.
Сейчас DSL может использоваться для описания класса агента. Так, вместо макросов:
SOL4_CLASS_START( a_creature_t )
SOL4_EVENT( evt_start )
SOL4_EVENT_STC( evt_meet_completed,
a_meeting_place_t::msg_meet_completed )
SOL4_STATE_START( st_normal )
SOL4_STATE_EVENT( evt_start )
SOL4_STATE_EVENT( evt_meet_completed )
SOL4_STATE_FINISH()
SOL4_CLASS_FINISH()
возможно более компактное описание:
{agent_class a_creature_t
{event evt_start}
{event evt_meet_completed
{by msg_meet_completed {of a_meeting_place_t} } } }
{state st_normal
{events
evt_start
evt_meet_completed } }
}
Это описание помещается в отдельный файл (например, a_creature.sol5) и обрабатывается специальным транслятором DSL. На выходе транслятора получается набор hpp/cpp файлов, которые подключаются в основной исходный текст через директивы #include. (В случае отличных от C++ языков программирования на выходе могут быть какие-нибудь классы/интерфейсы, которые содержат вспомогательный код для SObjectizer-а).
Сейчас SObjectizer Run-Time вынужден обрабатывать динамическое изменение состава классов агентов и их взаимосвязей. Поскольку за макросами SOL4_* скрываются глобальные переменные, которые в собственных конструкторах передают в системный словарь SObjectizer-а описания классов агентов. Возможность динамической загрузки/выгрузки DLL с агентами приводит к ситуации, когда SObjectizer должен реагировать на появление и исчезновения классов агентов. Что особенно сложно при обработке наследования для агентов и мержинге состояний агентов.
При наличии же DSL эту работу может выполнять транслятор DSL. Так, если агент использует наследование и мержинг состояний, то все конфликты будут выявлены еще при компиляции, а не при запуске приложения. Для этого при описании в DSL класса-наследника нужно будет сделать для транслятора доступными описания базовых классов. Например, такое описание:
SOL4_CLASS_START( a_derived_t )
SOL4_SUPER_CLASS( a_base_t )
SOL4_INITIAL_STATE( st_normal )
SOL4_MSG_START( msg_2, a_derived_t::msg_2 )
SOL4_MSG_FINISH()
SOL4_EVENT( evt_msg_2 )
SOL4_STATE_START( st_normal )
SOL4_STATE_MERGE( a_base_t, st_normal )
SOL4_STATE_EVENT( evt_msg_2 )
SOL4_STATE_FINISH()
SOL4_CLASS_FINISH()
в DSL может выглядеть как:
{include "a_base.sol5" }
{agent_class a_derived_t
{super a_base_t }
{initial_state st_normal }
{message msg_2 {of a_derived_t::msg_2} }
{event evt_msg_2}
{state st_normal
{merge {with st_normal} {from a_base_t}}
{events evt_msg_2}
}
}
Поскольку транслятор DSL будет иметь всю необходимую информацию, то в сгенерированном вспомогательном коде будет находиться полностью готовое описание класса агента для SObjectizer. Это описание уже не нужно будет изменять во время работы SObjectizer.
Таким образом из SObjectizer Run-Time изрядный кусок работы будет перемещен в транслятор DSL. Что благоприятно скажется на объеме и сложности ядра SObjectizer. Например, транслятор DSL может быть написан на одном языке программирования (Ruby, к примеру), а ядро SObjectizer может быть реализовано для ряда других языков (C++, D, Scala).
Справедливую критику в SObjectizer вызывает то, что для идентификации сообщений, событий и состояний в SObjectizer используются строки. Хотя строки зарекомендовали себя как очень удобный подход (в частности, имена состояний и сообщений можно формировать во время исполнения, имена сообщений можно передавать через SOP из-вне приложения), все же хочется иметь статический контроль компилятора. Достичь этого можно, если транслятор DSL будет генерировать специальные вспомогательные классы/методы/объекты, являющиеся, по сути, обертками над текстовыми именами.
Например, если в DSL описании указано:
{agent_class a_meeting_place_t
{message msg_creature_in {of ... } }
{message msg_meet_completed {of ...} }
...
{event evt_first_in {by msg_creature_in {of {self}}} }
{event evt_second_in {by msg_creature_in {of {self}}} }
...
{state st_empty {events evt_first_in}}
{state st_first_in {events evt_second_in}}
...
}
то в коде агента a_meeting_place_t можно будет использовать следующие имена:
void a_meeting_place_t::so_on_subscription()
{
// Подписка событий на сообщения.
so_events().evt_first_in().subscribe(
so_messages().msg_creature_in() );
so_events().evt_second_in().subscribe(
so_messages().msg_creature_in() );
}
void a_meeting_place_t::evt_first_in( const msg_creature_in & cmd )
{
...
// Переход в состояние.
so_states().st_first_in().switch_to();
}
void a_meeting_place_t::evt_second_in( const msg_creature_in & cmd )
{
...
// Отсылка сообщения.
auto_ptr< msg_meet_completed > msg1 =
so_messages().msg_meet_complete().make();
msg1->... // Инициализация.
msg1->send();
...
// Переход в состояние.
so_states().st_empty().switch_to;
}
в грядущем C++09 или в D/Scala (в общем, если доступен вывод типов) работа с сообщениями может стать еще компактнее:
// Например, в D. auto msg1 = soMessages.msgMeetComplete.make; ... msg.send;
Главное достоинство этого подхода в том, что изменение имени сообщения/события/состояния в DSL автоматически сделает исходный код некомпилирируемым. И компилятор сам будет указывать разработчику, какие фрагменты необходимо переделать.
В подавляющем количестве случаев заранее известно, на чьи сообщения агент будет подписываться (в многих случаях это его собственные сообщения). И подписка не изменяется в течении жизни агента. Поэтому, если предоставить возможность в DSL перечислить имена инцидентов событий, то код so_on_subscription может быть сгенерирован транслятором DSL:
{agent_class a_meeting_place_t
{message msg_creature_in {of ... } }
{message msg_meet_completed {of ...} }
{message msg_creature_report {of ...} }
...
{event evt_first_in {by msg_creature_in {of {self}}} }
{event evt_second_in {by msg_creature_in {of {self}} }
{event evt_creature_report {by msg_creature_report {of {self}}} }
...
}
{agent_class a_creature_t
{event evt_start {by msg_start {of {sobjectizer_agent}}}}
{event evt_meet_completed
{by msg_meet_completed {of a_meeting_place_t}
{owner_name_from_expression "m_meeting_place_name"}}}
...
}
что приведет к:
void a_meeting_place_t::so_on_subscription()
{
so_events().evt_first_in().subscribe(
so_query_name(),
so_messages().msg_creature_in() );
so_events().evt_second_in().subscribe(
so_query_name(),
so_messages().msg_creature_in() );
so_events().evt_creature_report().subscribe(
so_query_name(),
so_messages().msg_creature_report() );
}
void a_creature_t::so_on_subscription()
{
so_events().evt_start().subscribe(
so_4::rt::sobjectizer_agent_name(),
so_4::rt::sobjectizer_agent_t::so_messages().msg_start() );
so_events().evt_meet_completed.subscribe(
m_meeting_place_name,
a_meeting_place_t::so_messages().msg_meet_completed() );
}
Естественно, что будет предложен какой-то способ разбавить автоматически генерируемый so_on_subscription кодом разработчика для случаев, когда детали подписки выясняются только в процессе работы приложения.
Сейчас для агента нужно сделать полностью корректное описание C++ класса агента, после чего нужно продублировать часть этого описания в макросах SOL4_*. Но часть этой работы может сделать транслятор DSL. Так, если все события описаны в DSL, то вместо ручного описания прототипов обработчиков событий в hpp-файле можно будет в тело C++ класса подставить инструкцию #include:
class a_meeting_place_t : public so_4::rt::agent_t
{
#include "a_meeting_place.sol5.hpp"
...
};
В C++ можно было скрывать довольно сложные конструкции за макросами. В D/Java/Scala/C#/Eiffel макросов нет. Поэтому для них DSL представляется чуть ли не единственным удобным способом описания деталей агентов для SObjectizer-а.
Например, для D транслятор DSL может генерировать фрагмент кода, который разработчик подключает в тело класса агента через mixin:
class AMeetingPlace : so5.rt.Agent
{
// Подключение как шаблона.
mixin AMeetingPlace_SObjPart;
// Или подключение как текстового mixin-а.
mixin( import( "a_meeting_place.sol5.d" ) );
}
Для Scala можно генерировать trait-ы, который будет подмешиваться к классу агента:
class AMeetingPlace extends so5.rt.Agent with AMeetingPlace_SObjPart
{ ... }
Аналогично для Eiffel-я можно будет генерировать вспомогательный класс, который будет наследоваться классом агента:
class A_MEETING_PLACE inherit AGENT A_MEETING_PLACE_SOBJ_PART feature ... end
Для C# 2.0, может быть, получится использовать т.н. partial classes.
В тексте использованы фрагметы реализации теста chameneos для SObjectizer v.4.4.