Boost logo

Boost :

Subject: Re: [boost] Is there any interest in a library for actor programming?
From: Dominik Charousset (dominik.charousset_at_[hidden])
Date: 2013-10-15 11:50:34


On 06.07.2013, at 15:36, Dave Abrahams <dave_at_[hidden]> wrote:
> on Tue Jun 11 2013, David Sankel <camior-AT-gmail.com> wrote:
>
>> On Tue, Jun 11, 2013 at 3:31 PM, james <james_at_[hidden]> wrote:
>>
>>> On 11/06/2013 13:03, Brandon Kohn wrote:
>>>
>>>> On 6/10/2013 4:30 PM, james wrote:
>>>>
>>>>> I don't think that the limited correctness you get from that is very
>>>>> useful in a concurrent system.
>>>>>
>>>>
>>>> Why not? I think the development crews that worked on the mars orbiter
>>>> mission which failed would beg to differ.
>>>>
>>>
>>> [I]t all goes back to schema evolution, and that's why systems like
>>> protobuf tend to sacrifice clean syntax for extensibility..
>>>
>>
>> I think that goes back to my original point. For a large subset of users,
>> schema evolution is a non-issue—the entire system is deployed at once. A
>> Boost.Actor library should have a good story for these users IMO.
>
> +1
>
> --
> Dave Abrahams

So... It's been a while and did indeed cause more implementation work than I had expected, but typed actors are finally ready to fly. A short example:

------------------------------------------------
// no class => actor from match expression
auto p0 = spawn_typed(
  on_arg_match >> [](int a, int b) {
   return static_cast<double>(a) * b;
  },
  on_arg_match >> [](double a, double b) {
    return make_cow_tuple(a * b, a / b);
  }
);
// equal class-based implementation
class foo : public typed_actor<replies_to<int, int>::with<double>,replies_to<double, double>::with<double, double>> {
 protected:
  // override inherited, pure virtual member function
  behavior_type make_behavior() final {
    return (
      on_arg_match >> [](int a, int b) {
        return static_cast<double>(a) * b;
      },
      on_arg_match >> [](double a, double b) {
        return make_cow_tuple(a * b, a / b);
      };
    );
  }
};
...
auto p0 = spawn_typed<foo>();
------------------------------------------------

The newly spawned actor addressed by "p0" has the type typed_actor_ptr<replies_to<int, int>::with<double>,replies_to<double, double>::with<double, double>>. A value returned from a message handler is implicitly used as response message. This now true for all types of actors (and the old 'reply' function is hence deprecated).

Typed actors are not allowed to use the "become()" API, because this would change its type. And without proper compiler support, we cannot traverse the AST to look "inside" the message handlers and parse each call to "become()" to extract all possible message types. Furthermore, typed actors are not allowed to use guards. Either it handles any input of a type or none.

The "replies_to<..>::with<..>" syntax may be a bit verbose, but I think a self-explanatory type is worth some typing. This type is of course assignable to any of its subtypes:

------------------------------------------------
typed_actor_ptr<replies_to<int, int>::with<double>> p1 = p0; // ok

typed_actor_ptr<replies_to<double, double>::with<double, double>> p1 = p0; // ok

typed_actor_ptr<replies_to<int>::with<double>> p3 = p0; // compiler error
------------------------------------------------

When using a typed interface, the compiler is now able to type-check messaging:

------------------------------------------------
send(p0, 42); // <- compiler error

send(p0, 42, 24); // <- ok

sync_send(p0, 1, 2, 3).then ... // <- compiler error

sync_send(p0, 1, 2).then(
  [](float) { ... } // <- compiler error: expected double
);

sync_send(p0, 1, 2).then(
  [](double d) { ... } // <- ok
);
------------------------------------------------

By the way, another big change in the library is that match expressions now handle result types properly:

------------------------------------------------
auto res = match(something) (
  on("hello") >> [] { return 42; },
  on("goodbye") >> [] { return "why are you leaving?"; }
);
if (res.is<int>()) { ... } else { ... }
------------------------------------------------

That's also a plus when releasing the pattern matching as standalone boost library. Of course, "res" can be inspected using a visitor as well. It is basically a variant type that is either none_t or any result type from one of the message handlers (void is mapped to void_t to be able to distinct between "no match" and "invoked a void function").

There are currently two "missing" features for typed actors: typed remote actors and priorities. However, this is merely ongoing implementation work and is not by design.

Since type-safety was the major (the only?) concern regarding the library, I would be happy to hear feedback and suggestions. Since most of the "heavy" implementation work is done, I would focus on integrating the library into boost (i.e. use other boost components wherever possible) and using boost namespaces next.


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk