Boost logo

Boost :

Subject: Re: [boost] phoenix::bind
From: Giovanni Piero Deretta (gpderetta_at_[hidden])
Date: 2008-10-02 08:16:27


On Thu, Oct 2, 2008 at 1:10 PM, Joel de Guzman
<joel_at_[hidden]> wrote:
> Giovanni Piero Deretta wrote:
>
>>> No, that is not the "unlambda" semantics. There needs to be 2 function
>>> application there: 1) the one that substitutes the stl container for _1
>>> (the leftmost _1) and 2) the one that substitutes the container's
>>> element for _1 (the rightmost _1). The key point here is that phx.lambda
>>> (and BLL protect), *returns a lambda-functor that returns a
>>> lambda-functor*.
>>> unlambda OTOH simply returns a plain functor (non-actor). A plain functor
>>> can only do one function application (bear in mind that this is phoenix
>>> for_each (lazy), not std::for_each (eager)).
>>
>> Ok, follow me:
>>
>> let 'f' me an unary function object, one suitable as a parameter to
>> for_each:
>> Am I right that the following calls should work and apply every
>> element of some range to 'f'?
>>
>> phx::for_each(_1, f)(some_range);
>
> No. Not unless you bind f.
>

OK, I'm really really missing something:

#include <iostream>
#include <vector>
#include <boost/spirit/include/phoenix_core.hpp>
#include <boost/spirit/home/phoenix/stl.hpp>
#include <boost/spirit/include/phoenix_operator.hpp>

using namespace boost::phoenix;

struct foo {
    typedef void result_type;
    void operator()(int x) const{
        std::cout << x << std::endl;
    }
};

namespace px = boost::phoenix;

int
main()
{
    std::vector<int> r(10, 1);
    foo f;
    px::for_each(px::arg_names::arg1, f)(r);
    return 0;

}

This compiles just fine and prints 1 ten times....
I do not need bind to use 'f', it is a perfectly fine unary function
object. Phoenix has no business messing with it :)

>> Ok, now 'f' is unlambda(std::cout << _1 << std::endl) (assuming
>> phoenix had unlambda with the same semantics of
>> boost::lambda::unlambda. Isn't the previous example still supposed to
>> work? From phoenix point of view, the internal lambda expression is
>> masked so it is treated as any other function object.
>
> No it won't work, unless you bind it. An arbitrary function object
> is not lazy -- it is no different than an ordinary function.

which is a perfectly good argument to for_each.

> f must be a lambda function, not an ordinary function (or function object).
> And, in addition to that, it must be lambda function that returns
> a lambda function.

No on both counts. The above example compiles just fine...
I feel that you are looking for complexity were there isn't ... I
think that phoenix needs to know about functional arguments only when
it need to do placeholder substitution on variable initialization.

>
>> The problem is of course that this prevent binding local lambda
>> variables to placeholders of the outer lambda expression: phoenix will
>> not recongnize the result of lambda[] as part of the lambda expression
>> but will treat it as an opaque function object, so it there is no
>> extra evaluation round for binding the actual arguments of the outer
>> lambda expression to the local variables. Still I'm sure you can come
>> out with a protocol to do that without requiring the extra round to be
>> shown to the user.
>
> The premise is flawed. Your assumptions are wrong. This is not
> the reason why there needs to be an extra evaluation round.
> In fact, the "extra round" is never shown to the user! It happens
> inside the phoenix function. Look:
>
> for_each(arg1,
> lambda(_a = arg2)
> [
> push_back(arg1, _a)
> ]
> )
>
> Do you see an extra function application there? No.

As I said elsewere, the extra round is hidden when lambda is used to
protect a nested expression, but it shows when used a top level:

lambda[ for_each(arg1, lambda(_a = arg2)[push_back(arg1, _a)])] () (r, i);

There is really really no need for the extra '()' there. There are no
local variables in the outer lambda and even if there were, the user
can initialize them with lambda(/* init here*/)[...]

>
>>> Here's the crucial difference (using BLL) highlighting the difference:
>>>
>>> {
>>> int x = 5;
>>> int r = bind(_1, _2)(unlambda(_1), x);
>>> BOOST_TEST(x == 5);
>>> }
>>>
>>> {
>>> int x = 5;
>>> int r = bind(_1, _2)(protect(_1)(), x);
>>> BOOST_TEST(x == 5);
>>> }
>>>
>>> Notice that protect *requires* the additional function application:
>>>
>>> protect(_1)()
>>>
>>
>> I understand the difference, but what buys us? What is the advantage?
>> It feels a bit like an internal detail leaking...
>
> Ask Jaakko. It's his interface. I can't speak for him.
> I'm only saying that it is not the same and you can't replace
> the one for the other. I'm also saying that it is the "protect"
> model that Phoenix is adopting, not unlambda. Hence, the
> misconception and misunderstanding.
>

Yes, and that is what I'm lobbying to change :)

-- 
gpd

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