Boost C++ Libraries Home Libraries People FAQ More

PrevUpHomeNext

Making Lazy Functions

If we limited ourselves to nothing but terminals and operator overloads, our embedded domain-specific languages wouldn't be very expressive. Imagine that we wanted to extend our calculator EDSL with a full suite of math functions like sin() and pow() that we could invoke lazily as follows.

// A calculator expression that takes one argument
// and takes the sine of it.
sin(_1);

We would like the above to create an expression template representing a function invocation. When that expression is evaluated, it should cause the function to be invoked. (At least, that's the meaning of function invocation we'd like the calculator EDSL to have.) You can define sin quite simply as follows.

// "sin" is a Proto terminal containing a function pointer
proto::terminal< double(*)(double) >::type const sin = {&std::sin};

In the above, we define sin as a Proto terminal containing a pointer to the std::sin() function. Now we can use sin as a lazy function. The default_context that we saw in the Introduction knows how to evaluate lazy functions. Consider the following:

double pi = 3.1415926535;
proto::default_context ctx;
// Create a lazy "sin" invocation and immediately evaluate it
std::cout << proto::eval( sin(pi/2), ctx ) << std::endl;

The above code prints out:

1

I'm no expert at trigonometry, but that looks right to me.

We can write sin(pi/2) because the sin object, which is a Proto terminal, has an overloaded operator()() that builds a node representing a function call invocation. The actual type of sin(pi/2) is actually something like this:

// The type of the expression sin(pi/2):
proto::function<
    proto::terminal< double(*)(double) >::type const &
    proto::result_of::as_child< double const >::type
>::type

This type further expands to an unsightly node type with a tag type of proto::tag::function and two children: the first representing the function to be invoked, and the second representing the argument to the function. (Node tag types describe the operation that created the node. The difference between a + b and a - b is that the former has tag type proto::tag::plus and the latter has tag type proto::tag::minus. Tag types are pure compile-time information.)

[Note] Note

In the type computation above, proto::result_of::as_child<> is a metafunction that ensures its argument is a Proto expression type. If it isn't one already, it becomes a Proto terminal. We'll learn more about this metafunction, along with proto::as_child(), its runtime counterpart, later. For now, you can forget about it.

It is important to note that there is nothing special about terminals that contain function pointers. Any Proto expression has an overloaded function call operator. Consider:

// This compiles!
proto::lit(1)(2)(3,4)(5,6,7,8);

That may look strange at first. It creates an integer terminal with proto::lit(), and then invokes it like a function again and again. What does it mean? Who knows?! You get to decide when you define an evaluation context or a transform. But more on that later.

Making Lazy Functions, Continued

Now, what if we wanted to add a pow() function to our calculator EDSL that users could invoke as follows?

// A calculator expression that takes one argument
// and raises it to the 2nd power
pow< 2 >(_1);

The simple technique described above of making pow a terminal containing a function pointer doesn't work here. If pow is an object, then the expression pow< 2 >(_1) is not valid C++. (Well, technically it is; it means, pow less than 2, greater than (_1), which is nothing at all like what we want.) pow should be a real function template. But it must be an unusual function: one that returns an expression template.

With sin, we relied on Proto to provide an overloaded operator()() to build an expression node with tag type proto::tag::function for us. Now we'll need to do so ourselves. As before, the node will have two children: the function to invoke and the function's argument.

With sin, the function to invoke was a raw function pointer wrapped in a Proto terminal. In the case of pow, we want it to be a terminal containing TR1-style function object. This will allow us to parameterize the function on the exponent. Below is the implementation of a simple TR1-style wrapper for the std::pow function:

// Define a pow_fun function object
template< int Exp >
struct pow_fun
{
    typedef double result_type;

    double operator()(double d) const
    {
        return std::pow(d, Exp);
    }
};

Following the sin example, we want pow< 1 >( pi/2 ) to have a type like this:

// The type of the expression pow<1>(pi/2):
proto::function<
    proto::terminal< pow_fun<1> >::type
    proto::result_of::as_child< double const >::type
>::type

We could write a pow() function using code like this, but it's verbose and error prone; it's too easy to introduce subtle bugs by forgetting to call proto::as_child() where necessary, resulting in code that seems to work but sometimes doesn't. Proto provides a better way to construct expression nodes: proto::make_expr().

Lazy Functions Made Simple With make_expr()

Proto provides a helper for building expression templates called proto::make_expr(). We can concisely define the pow() function with it as below.

// Define a lazy pow() function for the calculator EDSL.
// Can be used as: pow< 2 >(_1)
template< int Exp, typename Arg >
typename proto::result_of::make_expr<
    proto::tag::function  // Tag type
  , pow_fun< Exp >        // First child (by value)
  , Arg const &           // Second child (by reference)
>::type const
pow(Arg const &arg)
{
    return proto::make_expr<proto::tag::function>(
        pow_fun<Exp>()    // First child (by value)
      , boost::ref(arg)   // Second child (by reference)
    );
}

There are some things to notice about the above code. We use proto::result_of::make_expr<> to calculate the return type. The first template parameter is the tag type for the expression node we're building -- in this case, proto::tag::function.

Subsequent template parameters to proto::result_of::make_expr<> represent child nodes. If a child type is not already a Proto expression, it is automatically made into a terminal with proto::as_child(). A type such as pow_fun<Exp> results in terminal that is held by value, whereas a type like Arg const & (note the reference) indicates that the result should be held by reference.

In the function body is the runtime invocation of proto::make_expr(). It closely mirrors the return type calculation. proto::make_expr() requires you to specify the node's tag type as a template parameter. The arguments to the function become the node's children. When a child should be stored by value, nothing special needs to be done. When a child should be stored by reference, you must use the boost::ref() function to wrap the argument.

And that's it! proto::make_expr() is the lazy person's way to make a lazy funtion.


PrevUpHomeNext