Boost logo

Boost :

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


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk