Monday, April 16, 2012

Guards! Guards!

(no, this post is not about the Discworld novel)

Guards are a new feature in libcppa. More precisely, it's an adaption of Erlangs guard sequences for patterns. A guard is an optional expression that evaluates to either true or false for a given input (message). Guards can be used to constrain a given match statement by using placeholders (_x1 - _x9) as shown in the example below.
using namespace cppa;
using namespace cppa::placeholders;
receive_loop(
  on<int>().when(_x1 % 2 == 0) >> []() {
    // int is even
  },
  on<int>() >> []() {
    // int is odd
  }
);
Guard expressions are a lazy evaluation technique (somewhat like boost lambdas). The placeholder _x1 is substituted with the first value of an incoming message. You can use all binary comparison and arithmetic operators as well as "&&" and "||". In addition, there are two functions designed to be used in guard expressions: gref and gcall. The function gref creates a reference wrapper. It's similar to std::ref but it is always const and 'lazy'. A few examples to illustrate some pitfalls:
int ival = 42;
receive(
  on<int>().when(ival == _x1) // (1) ok, matches if _x1 == 42
  on<int>().when(gref(ival) == _x1) // (2) ok, matches if _x1 == ival
  on<int>().when(std::ref(ival) == _x1) // (3) ok, because of placeholder
  on<anything>().when(gref(ival) == 42) // (4) ok, matches everything as long as ival == 42
  on<anything>().when(std::ref(ival) == 42) // (5) compiler error
);
The statement std::ref(ival) == 42 is evaluated immediately and returns a boolean, whereas gref(ival) == 42 creates a guard expression. Thus, you should always use gref instead of std::ref to avoid subtle errors.

By the way, gref is a great way to reduce verbosity of receive loops:

bool done = false;
do_receive(
  // ...
  on<atom("shutdown")>() >> [&]() {
    done = true;
  }
)
.until(gref(done));
//equal to: .until([&]() { return done; })


The second function, gcall, encapsulates a function call. It's usage is similar to std::bind, but there is also a short version for unary functions: gcall(..., _x1) is equal to _x1(...).
auto vec_sorted = [](std::vector<int> const& vec) {
  return std::is_sorted(vec.begin(), vec.end());
};
receive(
  on<std::vector<int>>().when(gcall(vec_sorted, _x1)) // equal to:
  on<std::vector<int>>().when(_x1(vec_sorted))) // ...
);
Placeholders provide some more convenience member functions besides operator(). A few code snippets:
_x1.starts_with("hello")
_x1.size() > 10
_x1.in({"abc", "def"})
_x1.not_in({0, 10, 100})
_x1.empty()
_x1.not_empty()
_x1.front() == 42
A final note: you don't have to check if a container is empty before calling _x1.front(), because front() returns an option for a reference to the first element.

Have fun!