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-05-21 14:55:55


On 21.05.2013, at 02:02, Hartmut Kaiser <hartmut.kaiser_at_[hidden]> wrote:

>
>> There was some discussion about this topic after the talk at the C++Now
>> conference. I do agree that it is desirable to enable the compiler to
>> verify the correctness of actor programs at compile time. However, in
>> order to be able to ensure the correctness of a program, one would have to
>> define not only all possible input messages, but the response types
>> depending on the input as well. Consider this example:
>>
>> actor_type<reacts_to<atom("add"),int,int>::with<int>,
>> reacts_to<atom("hello")>::with<std::string>> my_actor() {
>> return (
>> on(atom("add"), arg_match) >> [](int a, int b) {
>> return a+b;
>> },
>> on(atom("hello")) >> [] {
>> return "world";
>> }
>> );
>> }
>>
>> It's easy to check whether the interface is fulfilled. On each send, the
>> compiler is able to check whether the given message matches actor_type and
>> if the sender of the message (more precisely, the receiver of the response
>> message) can handle the result. However, an approach like this cannot
>> allow an actor to change its behavior. Hence, something as simple as the
>> classical dining philosophers example
>> (https://github.com/Neverlord/libcppa/blob/master/examples/message_passing
>> /dining_philosophers.cpp) is not possible.
>>
>> So, what if we allow actors to set a subset of given actor_type as
>> behavior? Consider this example:
>>
>> struct my_actor : actor_type<reacts_to<atom("init">,
>> reacts_to<atom("add"),int,int>::with<int>,
>> reacts_to<atom("hello")>::with<std::string>>
>> {
>> void init() {
>> // check whether given match_expr defines a subset of defined
>> actor_type
>> become (
>> on(atom("init")) >> [=] {
>> become (
>> on(atom("add"), arg_match) >> [=](int a, int b) {
>> return a+b;
>> },
>> on(atom("hello")) >> [=] {
>> return "world";
>> }
>> );
>> }
>> );
>> }
>> }
>>
>> This approach only allows class-based actors, because we need to evaluate
>> the type information provided by 'this'. We would enable the compiler to
>> check for *some* errors, but we could not verify, at compile time, that an
>> actor actually implements its own interface, because we have no way the
>> check whether an actor implements a message handler for each message it
>> claims to be able to receive.
>>
>> Even worse, both approaches do not allow actors to send/receive messages
>> before replying to a request (unless message passing would be blocking,
>> which would put an end to scalability). If we go back to the reply() API
>> currently found in libcppa, we couldn't match request and response type.
>>
>>
>> In any case, we would no longer be able to converts threads to actors on-
>> the-fly:
>>
>> int main() {
>> auto worker = spawn(...);
>> // Um... what is the type of 'self'? Is it a valid receiver for the
>> response message?
>> send(worker, ...);
>> receive (
>> // again: what is 'self' allowed to receive/reply? ...
>> );
>> }
>>
>>
>> Long story short: a type-safe interface would be less powerful and would
>> require more boilerplate code. However, perhaps there is a middle ground
>> for doing type checks at runtime when compiled in debug mode?
>>
>> int my_actor() {
>> // macro, defined as nothing if not compiled in debug mode
>> assert_protocol(reacts_to<atom("add"),int,int>::with<int>,
>> reacts_to<atom("hello")>::with<std::string>);
>> }
>>
>> Perhaps it would be possible to include the first presented approach for
>> type-safe actors along with the (dynamic) default actor API. In this way,
>> we could compose type-safe subsystems of actors whenever a problem can be
>> solved using the limited API.
>
> Even if I have to admit that I don't understand all implication of what
> you're saying, I would like to assert that it is possible to expose a fully
> compile-time type-safe way of invoking remote functions (actors). Here is
> what we do in HPX (https://github.com/STEllAR-GROUP/hpx/):
>
> int foo(std::string s) { return boost::lexical_cast<int>(s); }
>
> typedef hpx::make_action<decltype(&foo), &foo>::type foo_action_type;
> foo_action_type foo_action;
>
> // 'synchronous' invocation
> cout << foo_action(remote_locality_id, "42"); // prints 42
>
>
> // asynchronous invocation
> hpx::future<int> f = hpx::async(foo_action, remote_locality_id, "42");
> // do other stuff
> cout << f.get(); // prints 42
>
>
> // fails compiling
> cout << foo_action(remote_locality_id, 42);
>
> The same (similar) works for invoking member functions of remote objects.

All operations in libcppa are network transparent. The problem is not to perform the type checks on messages, but to extract the type information in the first place. Basically, we cannot look inside of a function. So all calls to become() and reply() are "hidden" at compile time, unless we expose the actor's protocol through the function signature (in C++14, we can even suppress the actor_type<...> return type and let the compiler deduce it for us):

actor_type<reacts_to<atom("add"),int,int>::with<int>,
           reacts_to<atom("hello")>::with<std::string>> my_actor() {
   return (
       on(atom("add"), arg_match) >> [](int a, int b) {
           return a+b;
       },
       on(atom("hello")) >> [] {
           return "world";
       }
   );
}

Given the implementation of my_actor above, spawn(my_actor) could return a strongly typed actor, because we can deduce this type information from the signature.

Best regards,
Dominik


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