Friday, March 4, 2011

Pattern Matching

The following example illustrates how libcppa actually looks like:

using namespace cppa;

void slogger()
{
    receive(on<int>() >> [](int value) {
        reply((value * 20) + 2);
    });
}

int main(int, char**)
{
    auto sl = spawn(slogger);
    send(sl, 2);
    receive(on<int>() >> [](int value) {
        // prints 42
        cout << value << endl;
    });
    
    return 0;
}

Although this is a very short snippet, it shows two main goals of libcppa:

  1. The library should look like an (internal) DSL and in particular should not force the user to write any glue code (e.g. derive a class actor and override virtual functions, etc.).
  2. Any context (even main) is silently converted into an actor if needed.
Messages are matched against a pattern. In Erlang, those patterns may ask for types in the guard. In a strong typed language like C++ it makes obviously more sense to match the types first.

Pattern are defined as on<T0, T1, ..., TN>() >> X. Where T0...TN are the types and X is a Callable (function or functor) that should be invoked on a match.

One more snippet that plays a bit more with patterns:

#include <iostream>
#include "cppa/cppa.hpp"

using std::cout;
using std::endl;
using namespace cppa;

void foo()
{
    auto patterns =
    (
        on<int, anything, int>() >> [](int v1, int v2)
        {
            cout << "pattern1 { " << v1 << ", " << v2 << " }\n";
        },
        on<std::string>() >> [](const std::string& str)
        {
            cout << "pattern2 { \"" << str << "\" }\n";
        },
        on(std::string("1"), any_vals) >> []()
        {
            cout << "pattern3 { \"1\" }\n";
        },
        on(1, val<std::string>(), any_vals) >> [](const std::string& str)
        {
            cout << "pattern4 { \" 1, " << str << "\" }\n";
        }
    );
    // receive 4 messages
    for (int i = 0; i < 4; ++i) receive(patterns);
}

int main(int, char**)
{
    //auto f = spawn(foo);
    auto f = spawn(foo);
    // prints: pattern2 { "hello foo" }
    send(f, "hello foo");
    // prints: pattern3 { "1" }
    send(f, "1", 2, 3);
    // prints: pattern1 { 1, 3 }
    send(f, 1, "2", 3);
    //  prints: pattern4 { "2" }
    send(f, 1, "2", "3");
    await_all_others_done();
    return 0;
}

There are basically two ways to use on():

The first one is to match for types only. In this case, you don't pass any arguments to the function and specify your pattern via the template parameter list. See pattern 1 & 2 for an example.
anything is a wildcard expression matching any number - zero or more - of elements.

Passing other pointer or reference types will cause a compile-time error.

The second way is to match for values. See pattern 3 & 4 for an example. The global constexpr value any_vals has the equivalent meaning of anything in the template parameter list. If you want to match for a type only, use the val() template function at that position.

3 comments:

  1. It seems with the latest version of the library the code snippets need some tweaks.

    In the first snippet I had to change:
    actor sl = spawn(slogger);
    to:
    auto sl = spawn(slogger);

    and:
    sl.send(2);
    to:
    send(sl, 2);

    I think that was it and it works.

    ReplyDelete
  2. Pattern matching doesn't seem to work with this syntax anymore; should I try to figure it out or has it changed a lot?

    ReplyDelete
  3. Thank you for drawing my attention to this.

    The pattern matching implementation changed since I posted this example and I just forgot to update the post. Plus, there was a little bug which I fixed just now.

    However, the (fixed) example does compile and run now. Have fun!

    ReplyDelete