PrevUpHomeNext

The YAP Way

There are certain idioms that Boost.YAP is written to support. Before getting into the nuts and bolts of how Boost.YAP operates, let's define these idioms.

evaluate(transform())

This is the main idiom you'll see reinforced in the examples. The idea is that you capture an expression:

auto expr_0 = /* ... */ ;

then transform it one or more times:

auto expr_1 = boost::yap::transform(expr_0, my_transform_1);
auto expr_2 = boost::yap::transform(expr_1, my_transform_2);
// ...
auto expr_n = boost::yap::transform(expr_n_minus_1, my_transform_n);

and then finally you evaluate it:

auto const result = boost::yap::evaluate(expr_n);

Each call to transform() here produces a new Expression that can subsequently be transformed. This is conceptually similar to what happens inside many compilers. Capturing the expression is analogous to the compiler's parse; the transformations are analogous to optimization passes; and the evaluation is analogous to code generation.

This keeps the meaning of your code quite clear and easy to follow. For this reason, I think you should try to use Boost.YAP in this way when you can.

transform()-as-evaluate

This is a variant of evaluate(transform()), where the evaluate() call at the end is unnecessary, because the final (or perhaps only) transform does all the evaluation we need.

For instance, here is the get_arity transform object used in the Calc3 example (don't worry too much about the implementation — we'll return to this later in the docs in much greater detail):

struct get_arity
{
    // Base case 1: Match a placeholder terminal, and return its arity as the
    // result.
    template <long long I>
    boost::hana::llong<I> operator() (boost::yap::expr_tag<boost::yap::expr_kind::terminal>,
                                      boost::yap::placeholder<I>)
    { return boost::hana::llong_c<I>; }

    // Base case 2: Match any other terminal.  Return 0; non-placeholders do
    // not contribute to arity.
    template <typename T>
    auto operator() (boost::yap::expr_tag<boost::yap::expr_kind::terminal>, T &&)
    {
        using namespace boost::hana::literals;
        return 0_c;
    }

    // Recursive case: Match any expression not covered above, and return the
    // maximum of its children's arities.
    template <boost::yap::expr_kind Kind, typename... Arg>
    auto operator() (boost::yap::expr_tag<Kind>, Arg &&... arg)
    {
        return boost::hana::maximum(
            boost::hana::make_tuple(
                boost::yap::transform(
                    boost::yap::as_expr(std::forward<Arg>(arg)),
                    get_arity{}
                )...
            )
        );
    }
};

Here is how this might be used:

auto expr = 1_p * 2_p;
auto const arity = boost::yap::transform(expr, get_arity{});
static_assert(arity.value == 2, "Called with wrong number of args.");

In this case, transform() produces a non-Expression value, all by itself. We got our result without ever needing to call evaluate().

[Note] Note

Whether transform() returns an Expression or non-Expression is entirely up to the caller. The transform object passed as the second argument to transform() defines what transform()'s return type will be.


PrevUpHomeNext