$include_dir="/home/hyper-archives/boost/include"; include("$include_dir/msg-header.inc") ?>
Subject: Re: [boost] [yap] review part 3: tests + misc + summary
From: Zach Laine (whatwasthataddress_at_[hidden])
Date: 2018-02-21 17:11:30
On Wed, Feb 21, 2018 at 10:28 AM, Steven Watanabe via Boost <
boost_at_[hidden]> wrote:
> AMDG
>
> On 02/20/2018 11:16 PM, Zach Laine via Boost wrote:
> > On Tue, Feb 20, 2018 at 9:29 AM, Steven Watanabe via Boost <
> > boost_at_[hidden]> 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
> 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_tag<yap::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