Boost logo

Boost :

Subject: Re: [boost] phoenix::bind
From: Joel de Guzman (joel_at_[hidden])
Date: 2008-09-30 23:32:47

Peter Dimov wrote:

>>> With boost::bind and a suitably defined f, one can do
>>> boost::bind( f, 0, 0, _1 )
>>> to approximate a lambda with two local variables, initially 0, and
>>> one argument.
>> That's a nice trick! That can be quite useful on certain occasions.
> If you manage to include both Phoenix and boost::bind, you can do a
> generator function that returns 1, 2, 3... with:
> boost::bind<int>( ++arg1, 0 )
> Of course if you have Phoenix you should be able to do the same with
> lambda( _a = 0 )[ ++_a ]
> but it doesn't seem to work. Maybe I'm doing something wrong. :-)

Maybe you want:

     let(_a = 0)[ ++_a ]

and herein I think lies the general misconception about Phoenix
lambda (and possibly a flaw in the general API).

The bottom line is that lambda introduces a new scope. It is
somewhat a cousin of BLL protect in that the lambda[...]. It
returns a lambda function that returns a lambda function.
I once called it the lambda-lambda. It came about when trying
to implement this:

        - \ \
double - /\ f . /\ x. f(f x)
       - / \ / \

in the original Phoenix1 implementation. Later, I realized it
is the same motivation behind protect, albeit in a less general
sense (from the lambda docs):

     "Primary motivation for including protect into the library,
      was to allow nested STL algorithm invocations (the section
      called “Nesting STL algorithm invocations”

Phoenix had lazy functions since its inception. The general problem
was how to implement a higher-order-function that accepts higher-
order-function. Like say:

     phx::for_each(_1, std::cout << _1 << std::endl)

substituting the left _1 for the container. The right _1 substitutes
the container's element which happens when for_each invokes the
input function for each element: f(element).

But that can't happen because all _1 will be substituted eagerly.
hence, it was necessary to brace the higher-order-function argument:

     phx::for_each(_1, lambda[std::cout << _1 << std::endl])

Notice that like protect, there are two function invocations happening
here. Each lambda introduces a new scope. Therefore for every lambda,
there is one more additional function invocation. Examples:

     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);

This is all in line with the motivation: to have a phoenix function
take in another phoenix (higher-order) function.

Now, in contrast, the phoenix "let" does not introduce another scope.

         int x = 1;
             let(_a = _1)
             (x) == x

         int x = 1, y = 10;
             let(_a = _1, _b = _2)
                 _a + _b
             (x, y) == x + y

It seems to me now that it is the "let" behavior (no new scopes introduced)
that folks like Giovanni needs for lambda (with or without the local
variables). It is also in line with your recent P.S.:

     lambda()[ ... ] should work and be an alias for lambda[ ... ]

(if we substitute lambda for let and allow in to have a null declaration).

Ok, so pardon me if this is long and winding. I'm brain storming myself
as I write this (short of talking out loud).

I think:

* The let behavior is what most people need. We can arrange it such
   that lambda assumes the let behavior, but only when placed in
   the outermost expression.
* Now what about higher order function arguments? I think it is still
   possible to introduce a new scope (ONLY IF) the lambda is placed
   inside an actor.

Let me see if I an come up with an update to V2 with these changes.


Joel de Guzman

Boost list run by bdawes at, gregod at, cpdaniel at, john at