четверг, 23 марта 2017 г.

[prog] А кому нужны типизированные агенты?

В комментариях ко вчерашней статье на Habr-е очень активно обсуждается тема типизированных акторов. Мол, проблема акторов в том, что они "нетипизированны", ты отсылаешь актору сообщение A, но не знаешь, может ли этот актор обрабатывать сообщения типа A или же он ждет сообщения типа B. А вот если бы акторы были типизированными, то мы бы имели не просто actor_reference, а actor_reference<B> и во время компиляции бы получили по рукам за попытку отослать актору сообщение типа A.

Признаться, я удивлен. По моему опыту, проблемы с типизацией сообщений (т.е. когда агенту по ошибке отсылается сообщение не того типа) -- это какая-то дивная экзотика. Гораздо чаще встречаются ситуации, когда агент не подписывается на сообщение, которое должен был бы обрабатывать. Ну вот должен был подписаться, но программист забыл это сделать. Еще более частая проблема -- это когда подписка на сообщение делается в одном состоянии, но не делается в другом.

Однако, это мой опыт, он не может служить адекватным мерилом. И раз люди о такой проблеме говорят, то возникает желание добавить в SO-5 дополнительную функциональность для того, чтобы удовлетворить еще и сторонников статической типизации. Но т.к. SO-5 уже не такой маленький проект и каждая новая фича, даже самая маленькая, увеличивает стоимость его развития и поддержки, то хочется понять для себя: а стоит ли овчинка выделки?

Посему вопрос: а кому хотелось бы иметь акторов с типизированными интерфейсами в фреймворках вроде Akka, CAF, SObjectizer, QP/C++ и пр.?

Под катом маленький пример того, как это может выглядеть в SObjectizer...

среда, 22 марта 2017 г.

[prog.c++.fantasy] Дополнительные атрибуты implies и expects в дополнение к noexcept

Недавняя тема "Не хватает мне в C++ noexcept-блоков с compile-time проверками" показала, что идея может оказаться востребованной, но пока она является еще очень сырой. Данный пост ставит целью сделать более понятное описание того, что же хочется получить. А так же показать, как эта идея может быть расширена для поддержки не только информации о выбрасывании исключений, но и произвольных атрибутов, которые нужны конкретным разработчикам.

Итак, сперва поговорим о том, почему мне хочется иметь что-то в дополнение к noexcept.

понедельник, 20 марта 2017 г.

[prog.thoughts] Не хватает мне в C++ noexcept-блоков с compile-time проверками

Написал давеча вот такой простой код на C++, с прицелом на обеспечение сильной гарантии exception safety:

void
stats_controller_t::turn_on()
   {
      std::lock_guard< std::mutex > lock{ m_lock };

      if( status_t::off == m_status )
         {
            const auto run_id = m_run_id + 1;

            send_next_message( very_small_timeout(), run_id );

// (1)
            m_status = status_t::on;
            m_run_id = run_id;
         }
   }

Тут в действиях до точки (1) может возникнуть исключение. Но это не страшно, т.к. никаких изменений в объект stats_controller_t внесено не было. А если мы нормально дошли до точки (1), то новое состояние объекта нужно зафиксировать. Что и происходит посредством двух простых операций присваивания.

На первый взгляд может показаться, что все нормально. Модификация состояния тривиальна и каких-то дополнительных телодвижений от разработчика не требуется. Однако, лично я бы предпочел иметь возможность написать что-то вроде:

void
stats_controller_t::turn_on()
   {
      std::lock_guard< std::mutex > lock{ m_lock };

      if( status_t::off == m_status )
         {
            const auto run_id = m_run_id + 1;

            send_next_message( very_small_timeout(), run_id );
// (1)
            noexcept
               {
                  m_status = status_t::on;
                  m_run_id = run_id;
               }
         }
   }

И чтобы компилятор дал бы мне по рукам, если бы в noexcept-блоке я бы написал какую-нибудь операцию, которая не помечена как noexcept. Зачем мне это нужно?

Затем, что код развивается и со временем, внося правки в какой-то кусок кода, разработчик может тупо не знать, под какие именно условия этот кусок кода затачивается. Например, спустя какое-то время какой-то новый разработчик добавит в noexcept-блок еще одно действие:

noexcept
   {
      m_status = status_t::on;
      store_status_change_timepoint();
      m_run_id = run_id;
   }

А это новое действие ба-бах! и начнет выбрасывать исключения. Тем самым послав изначальную сильную гарантию в /dev/null.

Но еще хуже то, что типы объектов могут меняться со временем. Так, сейчас у меня, допустим, m_run_id имеет тип int. А со временем он может быть заменен на какой-то "тяжелый" тип с бросающим исключения конструктором копирования. Т.е. внешне код в stats_controller_t::turn_on() остается таким же самым, но на практике сильная гарантия опять же отправится в /dev/null.

А вот если бы в C++ была возможность записать noexcept-блок и если бы C++ компилятор допускал бы там выполнение только noexcept-операций (т.е. элементарных действий вроде присваниваний или же разрешал вызов только noexcept-функций и методов), то можно было бы непосредственно в коде фиксировать условия, под которые рассчитан конкретный фрагмент кода.


Интересно: только я задумывался на тему таких noexcept-блоков в C++? Или же это еще кому-нибудь интересно? Спрашиваю потому, что есть C++ RG21 и, в принципе, данную идею можно запулить туда. Если она хоть кому-нибудь представляется стоящей.

PS. Старый блог-пост на связанную тему. Тогда я еще не имел представления о том, что именно будет означать noexcept в С++.

воскресенье, 19 марта 2017 г.

[prog.thougts] В очередной раз о нотации (в применении к C++)

В блоге я время от времени возвращаюсь к вопросу удобной нотации для C++ (например, в 2011-ом году и в 2014-ом). Но тогда вопрос нотации не имел такого уж серьезного значения. Сейчас же мы продвигаем и будем продвигать свои инструменты для C++ разработчиков во "внешний мир". Сложностей и препятствий здесь и так достаточно, поэтому не хочется создавать себе дополнительные проблемы на ровном месте. В частности, в виде непривычного для большинства C++ников стиля именования сущностей.

Дело в том, что в C++ нет общепринятого и стандартизированного соглашения о стиле оформления кода. На мой взгляд, это есть хорошо, но это мое личное мнение. Важнее то, что в C++ сообществе спокойно сосуществуют и активно используются совершенно разные стили именования. В STL и Boost-е, как мне кажется, традиционный C/C++ стиль. В Qt, wxWidgets и в POCO -- более привычный для Pascal/Delphi/VisualBasic/Java/C#. В библиотеке ACE вообще свой собственный, неповторимый стиль, заимствующий хорошие элементы как из snake_case, так и из CamelCase.

Мы же уже очень давно используем snake_case стиль, но с некоторыми очень важными дополнениями. В частности, у нас для имен типов используются суффикс _t. Например, у нас тип агента называется agent_t, а не agent. А тип сообщения называется message_t, а не message.

К суффиксу _t в мире C++, как мне думается, отношение довольно своеобразное. Давным-давно от суффикса _t стремились отказываться, т.к. это выглядело темным наследием plain old C. В C-шном коде суффикс _t давали, как правило, именам typedef-ов. Например, писали что-то вроде typedef struct my_type {...} my_type_t;.

Но в последние годы, после выхода C++11 и, особенно, после выхода C++14, суффикс _t в C++ опять возвращается, но уже в специфической роли. Например, начиная с C++14 в стандарт языка добавляются сокращенные определения, вроде enable_if_t<C,T> вместо enable_if<C,T>::type. Так что теперь в C++ для суффикса _t появляется вполне определенная нише. И использование данного суффикса для других целей способно запутать стороннего разработчика (разорвать шаблон, так сказать).

Чтобы быть "ближе к народу", мы у себя попробовали провести небольшой натурный эксперимент. И для одной своей новой разработки попробовали отказаться от _t в пользу традиционного для STL/Boost стиля именования сущностей.

Результат нам не понравился. И если при написании кода отсутствие суффикса _t хоть и мешает, но приспособиться можно, то вот при чтении кода имена типов без привычного уже суффикса _t крайне тяжело выделять из кода. Так что читать чужой код написанный в стиле STL/Boost значительно тяжелее, чем код в нашей привычной нотации. Посему эксперимент был признан неудачным, код мы вернули к старому оформлению. Причем решение вернуться назад мы приняли намного легче, чем решение провести этот самый эксперимент :)

Так что остаемся пока в рамках своей, служащей нам верой и правдой вот уже больше 16 лет нотации. Наверняка для кого-то из потенциальных пользователей это будет причиной не связываться с нашими разработками. Ну да и ладно, клиент, в общем-то, не всегда прав :)

PS. Если бы мне сейчас предстояло выбирать нотацию для C++ного кода, я бы взял уже привычную нотацию со snake_case, с использованием префиксов m_ для полей структур/классов и g_ для глобальных переменных. А вот для пространств имен и имен типов сделал бы небольшое изменение: первая буква в таких именах должна была бы быть заглавной. Получилось бы что-то вроде So_5::Impl::Simple_mtsafe_st_env_infrastructure_details::Actual_elapsed_timers_collector вместо текущего so_5::impl::simple_mtsafe_st_env_infrastructure_details::actual_elapsed_timers_collector_t (имена, кстати говоря, реальные).