123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322 |
- [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]
|