yap/doc/examples.qbk
2019-05-30 17:21:55 -05:00

323 lines
9.4 KiB
Plaintext

[section Examples]
Most of these examples are patterned after the examples from Boost.Proto. In
part, this was done to underscore where _yap_ can do what Proto can, and where
it cannot.
Where possible, a Proto-derived example uses syntax in `main()` identical to
that in the original Proto example.
If you don't know anything about Proto, don't worry. The examples are useful
on their own.
[section Hello World]
Remember how I mentioned earlier that _yap_ does things in a completely lazy
way? _yap_ doesn't ever evaluate your expression eagerly. Eager evaluation
can be done, but it's a bit of code.
[hello_world]
[endsect]
[section Hello World Redux]
That's better! Sort of.... We created a custom expression template with an
eager stream operator. This gives us eager evaluation, but gives away all the
lazy AST building-then-evaluating that we're using _ets_ for in the first
place. In this simple example, we don't really need it.
[hello_world_redux]
[endsect]
[section Minimal]
`minimal_expr` below models _ExprTmpl_; since it has no operators, an
expression must be built manually.
First, the template itself:
[minimal_template]
This can be used to make a `minimal_expr` plus expression:
[minimal_template_manual_construction]
You can evaluate, transform, or otherwise operate on `minimal_expr`
expressions using the functions in _yap_ that accept an _Expr_:
[minimal_template_evaluation]
[note Don't use _yap_ this way. Use the operator macros instead. This is an
example contrived only to show you the minimum requirements on a
_yap_-compatible template.]
[endsect]
[section Calc1]
This is the first of several calculator-building examples derived from Proto.
This first one just builds lazy expressions with placeholders, and evaluates
them. Here we can first see how much C++14-and-later language features help
the end user _emdash_ the Proto version is much, much longer.
[calc1]
[endsect]
[section Calc2]
The Proto Calc2 example turns the expressions from Calc1 into callable
objects. Using _yap_ you can do this in two ways.
You can just use lambdas to wrap the expressions:
[calc2a]
Or you can use _make_expr_fn_ to make a callable object from your expression:
[calc2b]
[endsect]
[section Calc3]
Here, we introduce a _XForm_ used to calculate expression arity, and
`static_assert()` that the number of parameters passed by the caller matches
the arity.
[note The `get_arity` _XForm_ doesn't produce an _Expr_, and it does not have
to. _XForms_ may produce _Exprs_ or arbitrary values. They may also have
arbitrary side effects, and may be stateful.]
[calc3]
[endsect]
[section Lazy Vector]
Finally, it starts to get interesting! This example shows how you can add
plus and other operations to sequences of data without creating temporaries
and allocating memory.
[note In this example, we see a terminal type that owns the storage of its
value, a `std::vector<double>`. See the Vector example later on to see a
terminal type that does not.]
[lazy_vector]
[endsect]
[section Self-Evaluating Expressions]
In most of the examples, we've seen _yap_ expressions captured, transformed,
and/or evaluated either manually, or within certain operations that always do
certain transformations (as in the `operator[]` in the _lazy_vector_ example).
Sometimes, you want the transfrmations to happen just before a _yap_
expression is used by non-_yap_-aware code. At other times, you might want an
entire _yap_ expression to be evaluated if it appears by itself in a statement
(i.e. as an expression statement).
This example uses C++17's `if constexpr ()`, simply because it makes the
example shorter and easier to digest. The `if constexpr ()` bits are not
strictly necessary.
[self_evaluation]
[endsect]
[section TArray]
Proto refers to this as the "mini-library for linear algebra" example. It
shows how quite complicated expressions involving sequences can be evaluated
elementwise, requiring no temporaries.
[note The original Proto example used a terminal that contained an array of
three `int`s; _yap_ cannot represent this, and so this example uses a
`std::array<T, 3>` instead. _yap_ decays `int[3]` to `int *`, since that is
what is done in a C++ expression. See _how_treated_ for details.]
[tarray]
[endsect]
[section Vec3]
An example using 3-space vectors, a bit like the tarray example.
[vec3]
[endsect]
[section Vector]
So far we've only seen examples with custom terminals that own the values in
the expressions we operate on. What happens when you've got types that you
want to operate on, non-intrusively? Here's how you might do it with
`std::vector<>`s:
[vector]
[note Though this example only provides overloads for the operations we want
to define over `std::vector<>`s, the result of each of those operations is an
_expr_, which uses *all* the operator overloads. If we wanted to restrict the
operations on the results too, we could have defined a custom expression
template with the desired operations, and used that instead of _expr_ in the
operator macros.]
[endsect]
[section Mixed]
This is a lot like the previous Vector example, except that it operates on
`std::vector<>`s and `std::list<>`s in the same expression.
[mixed]
[endsect]
[section Map Assign]
An implementation of `map_list_of()` from Boost.Assign using _yap_.
[map_assign]
[note `map_list_of_expr` defines a generic call operator that matches any
call, including one with the wrong number of arguments. This could be fixed
by adding a `static_assert()` to the `map_list_of_expr` template, or by
hand-writing the call operator with SFNIAE or concept constraints.]
[endsect]
[section Future Group]
An implementation of Howard Hinnant's design for /future groups/.
[future_group]
[endsect]
[section Autodiff]
Here we adapt an [@https://en.wikipedia.org/wiki/Automatic_differentiation
automatic differentiation] library to use _yap_ for specifying the equations
it operates on.
Autodiff is a pretty small library, and doesn't cover every possible input
expression. What it covers is simple arithmetic, and the well-known functions
`sin`, `cos`, `sqrt`, and `pow`.
Here is how you would form an input to the library using its API. This is
taken from the test program that comes with the library.
[autodiff_original_node_builder]
I have a *lot* of trouble understanding what's going on here, and even more
verifying that the expression written in the comment is actually what the code
produces. Let's see if we can do better.
First, we start with a custom expression template, `autodiff_expr`. It
supports simple arithmetic, but notice it has no call operator _emdash_ we
don't want `(a + b)()` to be a valid expression.
[autodiff_expr_template_decl]
We're going to be using a lot of placeholders in our Autodiff expressions, and
it sure would be nice if they were `autodiff_expr`s and not _exprs_, so that
only our desired operators are in play. To do this, we define an operator
that produces placeholder literals, using the _literal_op_m_ macro:
[autodiff_expr_literals_decl]
Now, how about the functions we need to support, and where do we put the call
operator? In other examples we created terminal subclasses or templates to
get special behavior on terminals. In this case, we want to create a
function-terminal template:
[autodiff_function_terminals]
`OPCODE` is an enumeration in Autodiff. We use it as a non-type template
parameter for convenience when declaring `sin_` and friends. All we really
need is for the `OPCODE` to be the value of the terminals we produce, and for
these function-terminals to have the call operator.
[note Using _member_call_m_ is a bit loose here, because it defines a variadic
template. We could have written unary call operators to ensure that the user
can't write call expressions with the wrong number of arguments.]
Now, some tranforms:
[autodiff_xform]
We need a function to tie everything together, since the transforms cannot
fill in the values for the placeholders.
[autodiff_to_node]
Finally, here is the _yap_ version of the function we started with:
[autodiff_yap_node_builder]
[endsect]
[section Transforming Terminals Only]
Sometimes it can be useful only to transform the terminals in an expression.
For instance, if you have some type you use for SIMD operations called
`simd<double>`, you may want to replace all the `double` terminals with
`simd<double>`. Perhaps you just want to change out `double` for `float`, or
`int` for `std::size_t`. You get the idea.
In this example, we're replacing all the terminals with something essentially
arbitrary, the sequence of integer terminals `N, N + 1, N + 2, ...`. This
makes it easier to observe the result of the replacement in a simple example.
[transform_terminals]
[endsect]
[section Pipable Algorithms]
Let's revisit the pipable standard algorithm example from the intro. Here's
how you might implement it.
[pipable_algorithms]
[endsect]
[section Boost.Phoenix-style `let()`]
Boost.Phoenix has a thing called _let_. It introduces named reusable values
that are usable in subsequent expressions. This example is something simliar,
though not exactly like Phoenix's version. In Phoenix, a let placeholder is
only evaluated once, whereas the example below does something more like macro
substitution; each let-placeholder is replaced with its initializing
expression everywhere it is used.
This example uses C++17's `if constexpr ()`, simply because it makes the
example shorter and easier to digest. The `if constexpr ()` bits are not
strictly necessary.
[let]
[endsect]
[endsect]