primer.qbk
6.33 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
[section An Expression Template Primer]
What are _ets_ anyway? In short, _ets_ are templates that you write to
capture expressions so that they can be transformed and/or evaluated lazily.
An example of normal C++ expression is:
std::sqrt(3.0) + 8.0f
The compiler sees this and creates some representation of that expression
inside the compiler. This is typically an _ast_ (AST). The AST for the
expression above might be:
[$yap/img/ast.png]
This tree structure captures all the elements of the original C++ code. The
expression is a plus operation whose left side is a call to `std::sqrt(3.0)`
and whose right side is `8.0f`. The call to `std::sqrt(3.0)` is its own
expression subtree consisting of a call node and its argument node.
A _yap_ version of this same tree is:
[$yap/img/expr.png]
The `operator+()` is represented by a _yap_ expression whose kind is
`yap::expr_kind::plus` and the call is represented by a _yap_ expression whose
kind is `yap::expr_kind::call`. Notice that the call expression has two
terminals, one for the callable, and one for its single argument.
The type that holds this expression is:
[plus_sqrt_yap_type]
That looks like a big mess; let's unpack it. You might notice that the
overall shape is the same as the expression tree diagram above. We have
tree-like nesting of `boost::yap::expression` template instantiations.
Here's the top-level `boost::yap::expression` again with
its noisy guts removed:
[plus_sqrt_yap_top_level_1]
// Left and right operand expressions ...
[plus_sqrt_yap_top_level_2]
It has an _kind_ of `plus` as its first template parameter (it's a non-type
parameter); this indicates what kind of "node" it is. In this case, the top
level expression is analogous to our `operator+()` AST node. Its operands are
the elements of its _tuple_ data member.
The left operand to the top-level plus operation is itself a _yap_ expression
representing `std::sqrt(3.0)`:
[plus_sqrt_yap_lhs]
This expression is a call expression. The first operand to the call
expression is the callable entity, in this case a pointer to `std::sqrt`. The
remaining operands are the arguments to pass to the callable; in this case,
there's only one operand after the callable, `3.0`.
The children of the `std::sqrt(3.0)` subexpression are terminals. This means
that they are leaf nodes in our notional AST.
The right operand to the top-level plus operation is of course also a _yap_
expression. It is also a terminal:
[plus_sqrt_yap_rhs]
Notice a couple of things here: 1) non-terminals (the top-level plus operation
and the call opertion in our example) have tuple elements that are *all* _yap_
expressions, and 2) terminals have tuple elements, *none of which* are _yap_
expressions (they're just normal types like `float` and `double (*)(double)`).
[note From here on, I'll use the terms "expression" and "node" interchangably,
and I'll also use the terms "subexpression" and "child" interchangably. Even
though _ets_ are not identical to tree-based ASTs, they're close enough that
the terminology is interchangable without loss of meaning.]
[heading Capturing an Expression]
If we want to capture an expression using _yap_ we have to do something to let
the compiler know not just to eagerly evaulate our expression, as it does when
it sees `std::sqrt(3.0) + 8.0f`.
To do this, we create _terminal_ expressions out of one or more of the
terminals in the expression we want to capture and evaluate lazily. Here,
I've declared a template alias to make that easier to type:
[plus_sqrt_term_alias]
And here is how I might use that alias to create the terminal containing
`std::sqrt`:
[plus_sqrt_yap_value]
The reason I can then just call the terminal with a `3.0` argument and add
`8.0f` to the result is that I'm taking a great big shortcut in this example
by using _yap_'s built-in example _et_, _expr_. _expr_ is a template with all
the operator overloads defined, including the call operator. Each operator
overload returns an _expr_, which is why the `+` in `std::sqrt(3.0) + 8.0f`
also works.
[note _expr_ is great for example code like what you see here, and it's great
for small _et_ use cases that are essentially implementation details. You
should write your own _ets_ for anything that is to be used in any other
context. The reason for this is that most of the time your _et_ system will
not want to support all combinations of all possible operators and function
calls. For instance, code like this:
(a + b) = c;
is at least unusual, if not outright wrong. Where does `c` go? Into `a`,
`b`, or into an expiring `a + b` temporary? What if `a` is a `std::string`
and `b` is a `FILE *`? _expr_ doesn't care. You probably want to design
interfaces that are more carefully considered than the "everything goes" style
implied by using _expr_. ]
_yap_ comes with a handy _print_ function. Calling it like this:
[print_plus_sqrt_yap_value]
Gives this output:
expr<+>
expr<()>
term<double (*)(double)>[=1]
term<double>[=3]
term<float>[=8]
This is a lot more readable. I show this to you here to give you a more
concise view of the AST-like structure.
(In case you're wondering why `&std::sqrt` is printed as the value `1`, so was
I. Apparently, that's just what GCC prints for that. Weird.)
[heading Doing Something Useful With It]
Now we've seen a simple expression both described as a C++ AST and captured as
a _yap_ expression. This just introduces the _et_ mechanism; what do we do
with it once we have an _et_? Consider one of the examples from the intro:
std::vector<int> v1 = {/* ... */};
std::vector<int> v2 = sort(v) | unique;
The rest of the tutorial will explain in greater detail how _yap_ can be used
in situations like this, but the brief version is this:
* Use _yap_ to capture an expression. In this case, something like `auto expr
= sort(v) | unique;`.
* Use the _yap_ _xform_ algorithm to transform the expression into what you
want. In this case, something like `auto desired_expr =
yap::transform(expr, my_transform);`, which turns the concise form `sort(v)
| unique` into the more verbose calls required by the standard algorithm
APIs. Note that the resulting expression can be transformed repeatedly if
this is desirable.
* Evauate the final expression, either using _eval_ or a call to _xform_ that
transforms the final expression into an evaluated result.
[endsect]