Boost logo

Boost :

Subject: Re: [boost] Is there any interest in a library for actor programming?
From: Hartmut Kaiser (hartmut.kaiser_at_[hidden])
Date: 2013-05-20 20:02:42


> 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.

Regards Hartmut
---------------
http://boost-spirit.com
http://stellar.cct.lsu.edu


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