Boost logo

Boost :

Subject: Re: [boost] [Phoenix] Some questions and notes...
From: Joel de Guzman (joel_at_[hidden])
Date: 2008-09-25 08:43:25


Giovanni Piero Deretta wrote:
> Hi all,
>
> I'm writing a review of phoenix, but first I have a some questions and notes.
>
> * First of all, it is not clear to me how 'lambda' works. I would have
> thought that it just introduced a new scope, but it seems that it does
> something more:
>
> 1: int i = 10;
> 2: cout << arg1(i) <<endl;
> 3: cout << lambda[ arg1 ](i) <<endl; // doesn't compile
> 4: cout << lambda[ arg1 ]()(i)
>
> The use of lambda at line 3 is completely superfluous, nevertheless, I
> prefer that syntax because it makes it clear that I'm using a lambda
> expression (always using lambda will also allow some other tricks I'll
> discuss later). Surprisingly, line 3 doesn't compile.
> lambda::operator[] actually returns a nullary stub that must be
> evaluated to actually get our unary function. Is this really
> necessary?

Yes.

> can it be fixed?

It's not broken. As Doug noted in his review, phoenix lambda
is like lambda protect (http://tinyurl.com/3sx7bo).
lambda[...] returns another lambda functor, hence for
each lambda, there's one function application.
Some samples from the tests:

1)
     int x = 1;
     int y = lambda[_1]()(x);
     BOOST_TEST(x == y);

2)
     int x = 1, y = 10;
     BOOST_TEST(
         (_1 + lambda[_1 + 2])(x)(y) == 1+10+2
     );
3)
     int x = 1;
     long x2 = 2;
     short x3 = 3;
     char const* y = "hello";
     zzz z;

     BOOST_TEST(lambda[_1](x)(y) == y);
     BOOST_TEST(lambda(_a = _1)[_a](x)(y) == x);
     BOOST_TEST(lambda(_a = _1)[lambda[_a]](x)(y)(z) == x);
     BOOST_TEST(lambda(_a = _1)[lambda[_a + _1]](x)(y)(x) == 2);
     BOOST_TEST(lambda(_a = _1)[lambda(_b = _1)[_a + _b + _1]](x)(x2)(x3) == 6);

So, walking through the first example (which resembles yours):

     lambda[_1]()(x);

The first call (empty) does no substitution and results
to:

     _1

The second call with x then substitutes x for _1.

Let's take another example (2):

     (_1 + lambda[_1 + 2])(x)(y)

The first call (with x) results in another lambda functor
after substituting the outer _1 for x and exposing the
expression inside the lambda without substitution:

     (x + _1 + 2)

The second call (with y) then does the final invocation:

     (x + y + 2)

The difference with lambda's protect is that phoenix' lambda
has true local variables and with the mechanism, inner lambda
scopes can actually get arguments and other information from
outer lambda scopes. This is important. For instance, the
example in the doc:

  write a lambda expression that accepts:

    1. a 2-dimensional container (e.g. vector<vector<int> >)
    2. a container element (e.g. int)

and pushes-back the element to each of the vector<int>.

cannot be done with protect alone.

> * non-standard result<> protocol. IMHO for Phoenix to become a first
> class boost library, it should support result_of out of the box. I
> understand that this is not done for backward compatibility, but I
> think that there are three solutions:
>
> 1) old users will still be able to use the Phoenix inside spirit if
> they have a large codebase that uses the old protocol.
> 2) phoenix could provide a wrapper that converts from the old to the
> new protocol and viceversa. This would require some changes in the
> client code.
> 3) phoenix could detect the protocol used by the user function and
> switch between the old and new 'result'. This is hard to do in a
> robust way, but breaks no user code.
>
> I would prefer the first option.

What's the first solution? It's not clear.

> * Perfect forwarding. I'm used to the perfect forwarding in boost
> lambda (which, contrary to what the documentation states, perfectly
> forwards up to 3 args). Personally have little use for lambdas that
> take their arguments by non const reference as I strive for
> referential transparency.

We will have perfect forwarding controlled by a macro.
The proto version has it. It's turned off by default
because even at a low expansion number, Eric notes that
it explodes.

> I know that implementing this feature has an high compile time cost,
> but there should at least be an option to enable it. I would vote for
> two additional 'lambda' syntaxes: clambda and plambda (better names
> are wellcome). The first one would work like boost lambda

That's a very good idea. Trivia: Phoenix started out pure.
No side-effects, only consts. It was good for me but people
just can't get over the scheme. It's not C++, they say.

Yes, I'd love to bring back some purity. Let me think about
it some more, ok?

> const_arguments and should have no compile time cost, while the second
> will do perfect forwarding up to some parameter number. This would
> require the lambda[] syntax to actually return the wrapped function
> object, not a stub (as per first question).

Good points. I'm adding these ideas in my notebook.

> * operator->*. I sometimes use this operator with boost lambda, as a
> short hand for binding member functions, but, as in phoenix, it
> requires it lhs to be a pointer. Would it possible to extend it to any
> functions? for example:
>
> struct foo {
> int bar;
> };
>
> std::vector<foo> in =...;
> std::vector<int> out;
>
> std::transform(in.begin(), in.end(), std::back_inserter(out),
> (&arg1)->*&foo::bar);
>
> The parenthesis, and ampersand are ugly, it would be great if this worked:
>
> std::transform(in.begin(), in.end(), std::back_inserter(out), arg1->*&foo::bar);

arg1->*&foo::bar is perfectly allowed! Why do you think it's not?
see /test/operator/member.cpp for examples on this. Some
examples:

     Test test = {1};
     const Test* cptr = &test;
     Test* ptr = &test;

     BOOST_TEST((val(ptr)->*&Test::value)() == 1);
     BOOST_TEST((val(cptr)->*&Test::value)() == 1);
     BOOST_TEST((arg1->*&Test::value)(cptr) == 1);

     ((val(ptr)->*&Test::value) = 2)();
     BOOST_TEST(test.value == 2);

     BOOST_TEST((val(ptr)->*&Test::func)(3)() == 3);
     BOOST_TEST((val(cptr)->*&Test::func)(4)() == 4);
     BOOST_TEST((val(ptr)->*&Test::dunc)()() == 10);

     BOOST_TEST((arg1->*&Test::func)(5)(ptr) == 5);

     shared_ptr<Test> sptr(new Test(test));

     BOOST_TEST((arg1->*&Test::value)(sptr) == 2);
     BOOST_TEST((arg1->*&Test::func)(6)(sptr) == 6);

> I think this would interfere with foo implementing its own
> operator->*. Which brings the question...
>
> * ... how do I implement type deduction for the my own overloaded
> operators? I.e. what is the phoenix equivalent of
> http://tinyurl.com/4botne ? By reading of the documentation I couldn't
> figure out an easy way to do it.

With V2, you can't (**). Phoenix V3 OTOH uses Boost Typeof. So it just
works on most compilers out of the box. If not, then Boost Typeof
has its own protocols for extending type deduction.

(**) Actually there is a way, but it's undocumented. I intentionally
didn't put more effort into this because I was certain to use
Boost.Typeof soon.

> Well, that's all for now, those questions are mostly to get the
> discussion rolling, more to come.

Thanks!

Regards,

-- 
Joel de Guzman
http://www.boostpro.com
http://spirit.sf.net

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