![]() |
Home | Libraries | People | FAQ | More |
So far, we've only seen examples of grammars with transforms that accept
one argument: the expression to transform. But consider for a moment
how, in ordinary procedural code, you would turn a binary tree into a
linked list. You would start with an empty list. Then, you would recursively
convert the right branch to a list, and use the result as the initial
state while converting the left branch to a list. That is, you would
need a function that takes two parameters: the current node and the list
so far. These sorts of accumulation problems are
quite common when processing trees. The linked list is an example of
an accumulation variable or state. Each iteration
of the algorithm takes the current element and state, applies some binary
function to the two and creates a new state. In the STL, this algorithm
is called std::accumulate()
.
In many other languages, it is called fold. Let's
see how to implement a fold algorithm with Proto transforms.
All Proto grammars can optionally accept a state parameter in addition
to the expression to transform. If you want to fold a tree to a list,
you'll need to make use of the state parameter to pass around the list
you've built so far. As for the list, the Boost.Fusion library provides
a fusion::cons<>
type from which you can build heterogeneous lists. The type fusion::nil
represents an empty list.
Below is a grammar that recognizes output expressions like cout_ <<
42 <<
'\n'
and puts the arguments into
a Fusion list. It is explained below.
// Fold the terminals in output statements like // "cout_ << 42 << '\n'" into a Fusion cons-list. struct FoldToList : proto::or_< // Don't add the ostream terminal to the list proto::when< proto::terminal< std::ostream & > , proto::_state > // Put all other terminals at the head of the // list that we're building in the "state" parameter , proto::when< proto::terminal<_> , fusion::cons<proto::_value, proto::_state>( proto::_value, proto::_state ) > // For left-shift operations, first fold the right // child to a list using the current state. Use // the result as the state parameter when folding // the left child to a list. , proto::when< proto::shift_left<FoldToList, FoldToList> , FoldToList( proto::_left , FoldToList(proto::_right, proto::_state) ) > > {};
Before reading on, see if you can apply what you know already about object, callable and primitive transforms to figure out how this grammar works.
When you use the FoldToList
function, you'll need to pass two arguments: the expression to fold,
and the initial state: an empty list. Those two arguments get passed
around to each transform. We learned previously that proto::_value
is a primitive transform that accepts a terminal expression and extracts
its value. What we didn't know until now was that it also accepts the
current state and ignores it. proto::_state
is also a primitive transform. It accepts the current expression, which
it ignores, and the current state, which it returns.
When we find a terminal, we stick it at the head of the cons list, using
the current state as the tail of the list. (The first alternate causes
the ostream
to be skipped.
We don't want cout
in
the list.) When we find a shift-left node, we apply the following transform:
// Fold the right child and use the result as // state while folding the right. FoldToList( proto::_left , FoldToList(proto::_right, proto::_state) )
You can read this transform as follows: using the current state, fold the right child to a list. Use the new list as the state while folding the left child to a list.
![]() |
Tip |
---|---|
If your compiler is Microsoft Visual C++, you'll find that the above
transform does not compile. The compiler has bugs with its handling
of nested function types. You can work around the bug by wrapping the
inner transform in
FoldToList( proto::_left , proto::call<FoldToList(proto::_right, proto::_state)> )
|
Now that we have defined the FoldToList
function object, we can use it to turn output expressions into lists
as follows:
proto::terminal<std::ostream &>::type const cout_ = {std::cout}; // This is the type of the list we build below typedef fusion::cons< int , fusion::cons< double , fusion::cons< char , fusion::nil > > > result_type; // Fold an output expression into a Fusion list, using // fusion::nil as the initial state of the transformation. FoldToList to_list; result_type args = to_list(cout_ << 1 << 3.14 << '\n', fusion::nil()); // Now "args" is the list: {1, 3.14, '\n'}
When writing transforms, "fold" is such a basic operation that Proto provides a number of built-in fold transforms. We'll get to them later. For now, rest assured that you won't always have to stretch your brain so far to do such basic things.