После анонса релиза SObjectizer 5.3.0 на forum.sources.ru завязалась интересная дискуссия по поводу сравнения SObjectizer и еще одной библиотеки с реализацией акторов для C++ -- libcppa. Про эту библиотеку я был наслышан раньше, поскольку если судить по списку реализаций модели актеров для C++ в Wikipedia, с 2012 года более-менее развивалось только два проекта: SObjectizer и libcppa. Но пристально смотреть было некогда. А вот сейчас довелось глянуть внимательнее...
Ну что тут сказать. Очень умные люди делают libcppa. Сделано столько, что это реально внушаить. На C++ных лямбдах и шаблонах сделан DSL для своеобразного аналога pattern-matching-а, да еще и с guard-ами. Вот, например:
on<int, anything>() >> [](int i) { // tuple with int as first element }, on(any_vals, arg_match) >> [](int i) { // tuple with int as last element // "on(any_vals, arg_match)" is equal to "on(anything{}, arg_match)" }, others() >> [] { // everything else (default handler) // "others()" is equal to "on<anything>()" and "on(any_vals)" } /**********************************************************************/ on<int>().when(_x1 % 2 == 0) >> [] { // int is even }, on<int>() >> [] { // int is odd } /**********************************************************************/ int val = 42; on<int>().when(_x1 == val) // (1) matches if _x1 == 42 on<int>().when(_x1 == gref(val)) // (2) matches if _x1 == val on<int>().when(_x1 == std::ref(val)) // (3) ok, because of placeholder others().when(gref(val) == 42) // (4) matches everything // as long as val == 42 |
Смотришь на такое и не можешь не восхищаться изобретательности авторов библиотеки. Они умудрились максимально похоже отобразить Erlang в C++. Вот честно, не уверен, что можно было бы сделать лучше.
Но, с другой стороны, постоянно задаешься вопросом: а зачем это все? Зачем переносить в C++ Erlang-овские приемы? Ведь языки совершенно разные. Да, новые фичи C++ позволяют писать лаконично. Но я постоянно ловлю себя на мысли, что когда я изучал диссертацию Джо Армстронга про Erlang, примеры кода на Erlang-а до меня доходили гораздо быстрее, чем примеры из документации по libcppa. Может я уже просто постарел и много чего позабыл, да и программировать разучился. Но вот этот пример я вкуривал долго. И не уверен, что до меня дошло. Поэтому объяснить на пальцах я его не возьмусь:
behavior server(event_based_actor* self) { auto die = [=] { self->quit(exit_reason::user_defined); }; return { on(atom("idle")) >> [=]() { auto worker = last_sender(); self->become ( keep_behavior, on(atom("request")) >> [=] { // forward request to idle worker self->forward_to(worker); // await next idle message self->unbecome(); }, on(atom("idle")) >> skip_message, others() >> die ); }, on(atom("request")) >> skip_message, others() >> die }; } |
В общем, первое впечатление -- ну очень навороченная штука. Но вот нужна ли она такая навороченная -- фиг знает. Может и не нужна. Хотя тем разработчикам, которые тащатся от Modern C++ Style, наверняка придется по вкусу.
Свое же знакомство с libcppa я продолжу. Надеюсь, получиться затем оформить свои мысли во что-то вроде сравнения SObjectizer и libcppa.
Ну а под катом пример того, до чего доходит сейчас использование лямбд в C++. Это фрагмент примера curl_fuse.cpp из штатных примеров libcppa. Понятно, что это демонстрация возможностей libcppa, а не промышленный код. Но все же... Когда-то был очень суровый overuse плюсовых шаблонов. Сейчас, же будет overuse лямбда-функций :(
Наброс-задачка для внимательных: сколько лямбда функций создается в этом фрагменте ;)
А так же вопрос: а вы сами готовы писать на C++ в таком стиле?
behavior make_behavior() override { print() << "init" << color::reset_endl; // spawn workers for(size_t i = 0; i < num_curl_workers; ++i) { m_idle_worker.push_back(spawn<curl_worker, detached+linked>(this)); } auto worker_finished = [=] { auto sender = last_sender(); auto i = std::find(m_busy_worker.begin(), m_busy_worker.end(), sender); m_idle_worker.push_back(*i); m_busy_worker.erase(i); print() << "worker is done" << color::reset_endl; }; print() << "spawned " << m_idle_worker.size() << " worker" << color::reset_endl; return ( on(atom("read"), arg_match) >> [=](const std::string&, uint64_t, uint64_t) { print() << "received {'read'}" << color::reset_endl; // forward job to first idle worker actor worker = m_idle_worker.front(); m_idle_worker.erase(m_idle_worker.begin()); m_busy_worker.push_back(worker); forward_to(worker); print() << m_busy_worker.size() << " active jobs" << color::reset_endl; if (m_idle_worker.empty()) { // wait until at least one worker finished its job become ( keep_behavior, on(atom("finished")) >> [=] { worker_finished(); unbecome(); } ); } }, on(atom("finished")) >> [=] { worker_finished(); } ); } |
Комментариев нет:
Отправить комментарий