323 lines
9.4 KiB
Plaintext
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]
|