On Wed, Feb 21, 2018 at 10:28 AM, Steven Watanabe via Boost < boost@lists.boost.org> wrote:
AMDG
On Tue, Feb 20, 2018 at 9:29 AM, Steven Watanabe via Boost < boost@lists.boost.org> wrote: <snip> That looks like a great candidate for an example, so I made one out of it:
https://github.com/tzlaine/yap/commit/4b383f9343a2a8affaf132c5be1eeb 99a56e58df
It took most of the examples from the documentation page you posted above, and it works for them, including nesting. For instance:
boost::yap::evaluate( let(_a = 1_p, _b = 2_p) [ // _a here is an int: 1
let(_a = 3_p) // hides the outer _a [ cout << _a << _b // prints "Hello, World" ] ], 1, " World", "Hello," );
That's verbatim from the Phoenix docs (except for the yap::evaluate() call of course), with the same behavior. The entire example is only 158
On 02/20/2018 11:16 PM, Zach Laine via Boost wrote: lines,
including empty lines an some comment lines. The trick is to make let() a regular eager function and leave everything else lazy Yap expression stuff. I don't know if this counts as evaluation in "a single pass" as you first mentioned, but I don't care, because the user won't care either -- she can't really tell.
[snip]
evaluate(let(_a = 1_p << 3) [ _a << "1", _a << "2" ], std::cout); // prints 3132, but should print 312
Why should that print out 312? Isn't equivalent to: std::cout << 3 << "1", std::cout << 3 << "2" ? If not, why not?
Also, let(_a=_a+_a)[let(_a=_a+_a)[let(_a=_a+_a)[...]]] has exponential cost.
Sure. It's also not allowed, though. From the let docs: The RHS (right hand side lambda-expression) of each local-declaration cannot refer to any LHS local-id. At this point, the local-ids are not in scope yet; they will only be in scope in the let-body. The code below is in error: let( _a = 1 , _b = _a // Error: _a is not in scope yet ) [ // _a and _b's scope starts here /*. body .*/ ] Checking this is an exercise left for the reader. :) [snip]
What does evaluate_with_context now do? Let's say expr is "a + b". Does
the context only apply to the evaluation of a and b as terminals, or does it apply to the plus operation as well?
It applies to the plus operation first. If the context has a handler for plus, then it's up to the context to handle recursion. If it does not, then it becomes evaluate_with_context(a, ctx, x...) + evaluate_with_context(b, ctx, x...)
Are such applications of the context conditional? How does the reader quickly grasp what the evaluate_with_context() call does? This seems like really muddy code to me. If you have something else in mind, please provide more detail -- I may of course be misunderstanding you.
My idea is that it would behave exactly like transform, except that the default behavior for nodes that are not handled by the context is to evaluate the operators instead of building a new expression.
Ah, I think I get it now. This is really a transform_evaluate() then? If so, that does sound useful. [snip]
I decided to conduct this experiment and see how it went. I removed the
terminal_value() function and all its uses from default_eval.hpp; this is all that was required to disable terminal unwrapping. Almost immediately, I ran into something I did not want to deal with. From one of the tests:
decltype(auto) operator()( yap::expr_tagyap::expr_kind::call, tag_type, double a, double b) { /* ... */ }
Personally, I believe that this is probably a very rare situation outside of test cases and toy examples.
This may prove to be true, though I'm not convinced now that it is (at least not the "very rare" part). If it is, I still think the current design is preferable to the alternative. This follows the "make simple things easy and complicated things possible" philosophy. Not unwrapping terminals is a lot more painful than I think you're allowing, in those cases where you *do* want to match terminals. [snip]
Actually... I have a much better idea. Why
don't you allow transform for a non-expr to match a terminal tag transform?
I've read this a few times now and cannot parse. Could you rephrase?
struct F { auto operator()(terminal_tag, int x) { return x * 2; } };
transform(3, F{}); // returns 6
This allows you to avoid the need for as_expr. I think this behavior is consistent, when you also unwrap terminals, as you would essentially be treating terminals and the raw values as being interchangeable.
I like this idea at first glance, but I'll need to look at the places where transform() applied to a non-Expression is acting as a no-op for a reason. Zach