calculator.qbk 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. [/
  2. / Copyright (c) 2008 Eric Niebler
  3. /
  4. / Distributed under the Boost Software License, Version 1.0. (See accompanying
  5. / file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
  6. /]
  7. [/=======================]
  8. [section Hello Calculator]
  9. [/=======================]
  10. "Hello, world" is nice, but it doesn't get you very far. Let's use Proto to build a
  11. EDSL (embedded domain-specific language) for a lazily-evaluated calculator. We'll
  12. see how to define the terminals in your mini-language, how to compose them into
  13. larger expressions, and how to define an evaluation context so that your
  14. expressions can do useful work. When we're done, we'll have a mini-language that
  15. will allow us to declare a lazily-evaluated arithmetic expression, such as
  16. `(_2 - _1) / _2 * 100`, where `_1` and `_2` are placeholders for values to be
  17. passed in when the expression is evaluated.
  18. [/=========================]
  19. [heading Defining Terminals]
  20. [/=========================]
  21. The first order of business is to define the placeholders `_1` and `_2`. For that,
  22. we'll use the _terminal_ metafunction.
  23. // Define a placeholder type
  24. template<int I>
  25. struct placeholder
  26. {};
  27. // Define the Protofied placeholder terminals
  28. proto::terminal<placeholder<0> >::type const _1 = {{}};
  29. proto::terminal<placeholder<1> >::type const _2 = {{}};
  30. The initialization may look a little odd at first, but there is a good reason
  31. for doing things this way. The objects `_1` and `_2` above do not require
  32. run-time construction -- they are ['statically initialized], which means they
  33. are essentially initialized at compile time. See the
  34. [link boost_proto.appendices.rationale.static_initialization Static
  35. Initialization] section in the [link boost_proto.appendices.rationale Rationale]
  36. appendix for more information.
  37. [/====================================]
  38. [heading Constructing Expression Trees]
  39. [/====================================]
  40. Now that we have terminals, we can use Proto's operator overloads to combine
  41. these terminals into larger expressions. So, for instance, we can immediately
  42. say things like:
  43. // This builds an expression template
  44. (_2 - _1) / _2 * 100;
  45. This creates an expression tree with a node for each operator. The type of the
  46. resulting object is large and complex, but we are not terribly interested in it right now.
  47. So far, the object is just a tree representing the expression. It has no
  48. behavior. In particular, it is not yet a calculator. Below we'll see how
  49. to make it a calculator by defining an evaluation context.
  50. [/==================================]
  51. [heading Evaluating Expression Trees]
  52. [/==================================]
  53. No doubt you want your expression templates to actually /do/ something. One
  54. approach is to define an ['evaluation context]. The context is like a function
  55. object that associates behaviors with the node types in your expression tree.
  56. The following example should make it clear. It is explained below.
  57. struct calculator_context
  58. : proto::callable_context< calculator_context const >
  59. {
  60. // Values to replace the placeholders
  61. std::vector<double> args;
  62. // Define the result type of the calculator.
  63. // (This makes the calculator_context "callable".)
  64. typedef double result_type;
  65. // Handle the placeholders:
  66. template<int I>
  67. double operator()(proto::tag::terminal, placeholder<I>) const
  68. {
  69. return this->args[I];
  70. }
  71. };
  72. In `calculator_context`, we specify how Proto should evaluate the placeholder
  73. terminals by defining the appropriate overloads of the function call operator.
  74. For any other nodes in the expression tree (e.g., arithmetic operations or
  75. non-placeholder terminals), Proto will evaluate the expression in the "default"
  76. way. For example, a binary plus node is evaluated by first evaluating the left
  77. and right operands and adding the results. Proto's default evaluator uses the
  78. _typeof_ library to compute return types.
  79. Now that we have an evaluation context for our calculator, we can use it to
  80. evaluate our arithmetic expressions, as below:
  81. calculator_context ctx;
  82. ctx.args.push_back(45); // the value of _1 is 45
  83. ctx.args.push_back(50); // the value of _2 is 50
  84. // Create an arithmetic expression and immediately evaluate it
  85. double d = proto::eval( (_2 - _1) / _2 * 100, ctx );
  86. // This prints "10"
  87. std::cout << d << std::endl;
  88. Later, we'll see how to define more interesting evaluation contexts and
  89. expression transforms that give you total control over how your expressions
  90. are evaluated.
  91. [/===================================]
  92. [heading Customizing Expression Trees]
  93. [/===================================]
  94. Our calculator EDSL is already pretty useful, and for many EDSL scenarios, no more
  95. would be needed. But let's keep going. Imagine how much nicer it would be if all
  96. calculator expressions overloaded `operator()` so that they could be used as
  97. function objects. We can do that by creating a calculator /domain/ and telling
  98. Proto that all expressions in the calculator domain have extra members. Here is how
  99. to define a calculator domain:
  100. // Forward-declare an expression wrapper
  101. template<typename Expr>
  102. struct calculator;
  103. // Define a calculator domain. Expression within
  104. // the calculator domain will be wrapped in the
  105. // calculator<> expression wrapper.
  106. struct calculator_domain
  107. : proto::domain< proto::generator<calculator> >
  108. {};
  109. The `calculator<>` type will be an expression wrapper. It will behave just like the
  110. expression that it wraps, but it will have extra member functions that we will
  111. define. The `calculator_domain` is what informs Proto about our wrapper. It is used
  112. below in the definition of `calculator<>`. Read on for a description.
  113. // Define a calculator expression wrapper. It behaves just like
  114. // the expression it wraps, but with an extra operator() member
  115. // function that evaluates the expression.
  116. template<typename Expr>
  117. struct calculator
  118. : proto::extends<Expr, calculator<Expr>, calculator_domain>
  119. {
  120. typedef
  121. proto::extends<Expr, calculator<Expr>, calculator_domain>
  122. base_type;
  123. calculator(Expr const &expr = Expr())
  124. : base_type(expr)
  125. {}
  126. typedef double result_type;
  127. // Overload operator() to invoke proto::eval() with
  128. // our calculator_context.
  129. double operator()(double a1 = 0, double a2 = 0) const
  130. {
  131. calculator_context ctx;
  132. ctx.args.push_back(a1);
  133. ctx.args.push_back(a2);
  134. return proto::eval(*this, ctx);
  135. }
  136. };
  137. The `calculator<>` struct is an expression /extension/. It uses `proto::extends<>` to effectively add additional members to an expression type. When composing larger expressions from smaller ones, Proto notes what domain the smaller expressions are in. The larger expression is in the same domain and is automatically wrapped in the domain's extension wrapper.
  138. All that remains to be done is to put our placeholders in the calculator domain. We do that by wrapping them in our `calculator<>` wrapper, as below:
  139. // Define the Protofied placeholder terminals, in the
  140. // calculator domain.
  141. calculator<proto::terminal<placeholder<0> >::type> const _1;
  142. calculator<proto::terminal<placeholder<1> >::type> const _2;
  143. Any larger expression that contain these placeholders will automatically be wrapped in the `calculator<>` wrapper and have our `operator()` overload. That means we can use them as function objects as follows.
  144. double result = ((_2 - _1) / _2 * 100)(45.0, 50.0);
  145. assert(result == (50.0 - 45.0) / 50.0 * 100));
  146. Since calculator expressions are now valid function objects, we can use them with standard algorithms, as shown below:
  147. double a1[4] = { 56, 84, 37, 69 };
  148. double a2[4] = { 65, 120, 60, 70 };
  149. double a3[4] = { 0 };
  150. // Use std::transform() and a calculator expression
  151. // to calculate percentages given two input sequences:
  152. std::transform(a1, a1+4, a2, a3, (_2 - _1) / _2 * 100);
  153. Now, let's use the calculator example to explore some other useful features of Proto.
  154. [/====================================]
  155. [heading Detecting Invalid Expressions]
  156. [/====================================]
  157. You may have noticed that you didn't have to define an overloaded `operator-()` or
  158. `operator/()` -- Proto defined them for you. In fact, Proto overloads /all/ the
  159. operators for you, even though they may not mean anything in your domain-specific
  160. language. That means it may be possible to create expressions that are invalid in
  161. your domain. You can detect invalid expressions with Proto by defining the
  162. /grammar/ of your domain-specific language.
  163. For simplicity, assume that our calculator EDSL should only allow addition,
  164. subtraction, multiplication and division. Any expression involving any other
  165. operator is invalid. Using Proto, we can state this requirement by defining the
  166. grammar of the calculator EDSL. It looks as follows:
  167. // Define the grammar of calculator expressions
  168. struct calculator_grammar
  169. : proto::or_<
  170. proto::plus< calculator_grammar, calculator_grammar >
  171. , proto::minus< calculator_grammar, calculator_grammar >
  172. , proto::multiplies< calculator_grammar, calculator_grammar >
  173. , proto::divides< calculator_grammar, calculator_grammar >
  174. , proto::terminal< proto::_ >
  175. >
  176. {};
  177. You can read the above grammar as follows: an expression tree conforms to the calculator grammar if it is a binary plus, minus, multiplies or divides node, where both child nodes also conform to the calculator grammar; or if it is a terminal. In a Proto grammar, _wild_ is a wildcard that matches any type, so `proto::terminal< proto::_ >` matches any terminal, whether it is a placeholder or a literal.
  178. [note This grammar is actually a little looser than we would like. Only placeholders and literals that are convertible to doubles are valid terminals. Later on we'll see how to express things like that in Proto grammars.]
  179. Once you have defined the grammar of your EDSL, you can use the _matches_ metafunction to check whether a given expression type conforms to the grammar. For instance, we might add the following to our `calculator::operator()` overload:
  180. template<typename Expr>
  181. struct calculator
  182. : proto::extends< /* ... as before ... */ >
  183. {
  184. /* ... */
  185. double operator()(double a1 = 0, double a2 = 0) const
  186. {
  187. // Check here that the expression we are about to
  188. // evaluate actually conforms to the calculator grammar.
  189. BOOST_MPL_ASSERT((proto::matches<Expr, calculator_grammar>));
  190. /* ... */
  191. }
  192. };
  193. The addition of the `BOOST_MPL_ASSERT()` line enforces at compile time that we only
  194. evaluate expressions that conform to the calculator EDSL's grammar. With Proto
  195. grammars, `proto::matches<>` and `BOOST_MPL_ASSERT()` it is very easy to give the
  196. users of your EDSL short and readable compile-time errors when they accidentally
  197. misuse your EDSL.
  198. [note `BOOST_MPL_ASSERT()` is part of the Boost Metaprogramming Library. To use it,
  199. just `#include <boost/mpl/assert.hpp>`.]
  200. [/=====================================]
  201. [heading Controlling Operator Overloads]
  202. [/=====================================]
  203. Grammars and `proto::matches<>` make it possible to detect when a user has created
  204. an invalid expression and issue a compile-time error. But what if you want to
  205. prevent users from creating invalid expressions in the first place? By using
  206. grammars and domains together, you can disable any of Proto's operator overloads
  207. that would create an invalid expression. It is as simple as specifying the EDSL's
  208. grammar when you define the domain, as shown below:
  209. // Define a calculator domain. Expression within
  210. // the calculator domain will be wrapped in the
  211. // calculator<> expression wrapper.
  212. // NEW: Any operator overloads that would create an
  213. // expression that does not conform to the
  214. // calculator grammar is automatically disabled.
  215. struct calculator_domain
  216. : proto::domain< proto::generator<calculator>, calculator_grammar >
  217. {};
  218. The only thing we changed is we added `calculator_grammar` as the second template
  219. parameter to the `proto::domain<>` template when defining `calculator_domain`. With
  220. this simple addition, we disable any of Proto's operator overloads that would
  221. create an invalid calculator expression.
  222. [/========================]
  223. [heading ... And Much More]
  224. [/========================]
  225. Hopefully, this gives you an idea of what sorts of things Proto can do for you. But
  226. this only scratches the surface. The rest of this users' guide will describe all
  227. these features and others in more detail.
  228. Happy metaprogramming!
  229. [endsect]