============================== Нужен ли SObjectizer-у DSL? ============================== :Author: Евгений Охотников :Contact: eao197 at intervale dot ru; eao197 at yahoo dot com :Date: 2007.07.22 .. _SObjectizer: http://sobjectizer.sourceforge.net .. _chameneos: http://eao197.narod.ru/sobjectizer/chameneos.cpp.html .. contents:: Преамбула ========= SObjectizer_-4 не использовал внешних языков для описания классов агентов поскольку первая версия SObjectizer-4 создавалась в очень сжатые сроки и на разработку специализированного языка (DSL -- Domain Specific Language) просто не было времени. Вероятно, на тот момент это было правильное решение, которое позволило получить работающий SObjectizer и применить его в реальных проектах. По прошествии пяти лет использования SObjectizer-4 возникает вопрос о то, каким будет SObjectizer-5. Возможно, следующие версии SObjectizer либо будут серьезно переделаны по сравнению с SObjectizer-4, либо вообще будут созданы для других языков программирования (D/C#/Java/Scala/Eiffel, конкретный язык еще не выбран). Поэтому есть смысл вернуться к идее внешнего DSL для SObjectizer. Для чего DSL? ============= Сейчас 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-а). Какие выгоды мог бы дать DSL? ============================= Упрощение SObjecizer Run-Time ----------------------------- Сейчас 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" ... }; Упрощение декларации связанных с SObjectizer деталей для не-C++ реализаций --------------------------------------------------------------------------------------- В 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. .. vim:ts=2:sw=2:sts=2:expandtab:tw=78:ft=rst: