четверг, 3 июля 2014 г.

[prog.thoughts] Первые более-менее серьезные впечатления от libcppa

После анонса релиза 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();
      }
  );
}

Комментариев нет: