What are the benefits of using Boost.Phoenix?

8k views Asked by At

I can not understand what the real benefits of using Boost.Phoenix.

When I use it with Boost.Spirit grammars, it's really useful:

double_[ boost::phoenix::push_back( boost::phoenix::ref( v ), _1 ) ]

When I use it for lambda functions, it's also useful and elegant:

boost::range::for_each( my_string, if_ ( '\\' == arg1 ) [ arg1 = '/' ] );

But what are the benefits of everything else in this library? The documentation says: "Functors everywhere". I don't understand what is the good of it?

5

There are 5 answers

3
alfC On

I'll point you out what is the critical difference between Boost.Lambda and Boost.Phoenix:

Boost.Phoenix supports (statically) polymorphic functors, while Boost.Lambda binds are always monomorphic.

(At the same time, in many aspects the two libraries can be combined, so they are not exclusive choices.)

Let me illustrate (Warning: Code not tested.):

Phoenix

In Phoenix a functor can converted into a Phoenix "lazy function" (from http://www.boost.org/doc/libs/1_54_0/libs/phoenix/doc/html/phoenix/starter_kit/lazy_functions.html)

struct is_odd_impl{
    typedef bool result_type; // less necessary in C++11
    template <typename Arg>
    bool operator()(Arg arg1) const{
        return arg1 % 2 == 1;
    }
};

boost::phoenix::function<is_odd_impl> is_odd;

is_odd is truly polymorphic (as the functor is_odd_impl). That is is_odd(_1) can act on anything (that makes sense). For example in is_odd(_1)(2u)==true and is_odd(_1)(2l)==true. is_odd can be combined into a more complex expression without losing its polymorphic behavior.

Lambda attempt

What is the closest we can get to this in Boost.Lambda?, we could defined two overloads:

bool is_odd_overload(unsigned arg1){return arg1 % 2 == 1;}
bool is_odd_overload(long     arg1){return arg1 % 2 == 1;}

but to create a Lambda "lazy function" we will have to choose one of the two:

using boost::lambda::bind;
auto f0 = bind(&is_odd_overload, _1); // not ok, cannot resolve what of the two.
auto f1 = bind(static_cast<bool(*)(unsigned)>(&is_odd_overload), _1); //ok, but choice has been made
auto f2 = bind(static_cast<bool(*)(long)>(&is_odd_overload), _1); //ok, but choice has been made

Even if we define a template version

template<class T>
bool is_odd_template(T arg1){return arg1 % 2 == 1;}

we will have to bind to a particular instance of the template function, for example

auto f3 = bind(&is_odd_template<unsigned>, _1); // not tested

Neither f1 nor f2 nor f3 are truly polymorphic since a choice has been made at the time of binding.

(Note1: this may not be the best example since things may seem to work due to implicit conversions from unsigned to long, but that is another matter.)

To summarize, given a polymorphic function/functor Lambda cannot bind to the polymorphic function (as far as I know), while Phoenix can. It is true that Phoenix relies on the "Result Of protocol" http://www.boost.org/doc/libs/1_54_0/libs/utility/utility.htm#result_of but 1) at least it is possible, 2) This is less of a problem in C++11, where return types are very easy to deduce and it can be done automatically.

In fact, in C++11, Phoenix lambdas are still more powerful than C++11 built-in lambdas. Even in C++14, where template generic lambdas are implemented, Phoenix is still more general, because it allows a certain level of introspection. (For this an other things, Joel de Guzman (developer of Phoenix) was and still is well ahead of his time.)

6
zvrba On

Functional programming in C++. It's hard to explain unless you have previously used a language with proper support for functional programming, such as SML. I tried to use Phoenix and found it nice, but very impractical in real-life projects because it greatly increases compilation times, and error messages are awful when you do something wrong. I rememeber getting a few megabytes of errors from GCC when I played with Phoenix. Also, debugging deeply nested template instantiations is a PITA. (Actually, these are also all the arguments against using most of boost.)

0
Merlyn Morgan-Graham On

I have never used Phoenix, but...

From the Phoenix Library docs:

The Phoenix library enables FP techniques such as higher order functions, lambda (unnamed functions), currying (partial function application) and lazy evaluation in C++

From the Wikipedia article on Functional programming:

... functional programming is a programming paradigm that treats computation as the evaluation of mathematical functions and avoids state and mutable data. It emphasizes the application of functions, in contrast to the imperative programming style, which emphasizes changes in state

So, Phoenix is a library for enabling Functional Programming in C++.

The major interest in Functional Programming these days seems to stem from the perceived advantages in correctness, and performance, due to limiting or eliminating side-effects.

Correctness, because without side-effects, the code that you see is everything going on in the system. Some other code won't be changing your state underneath you. You can much more easily write bug-free code in this sort of environment.

Performance, because without side-effects, the code you write can safely run in parallel, without any resource managing primitives, or atomic-access tricks. Multi-threading can be enabled extremely easily, even automatically, and operate extremely efficiently.

1
Anycorn On

Well, its a very powerful lambda language.

I used it to create a prototype for a math-like DSL:

http://code.google.com/p/asadchev/source/browse/trunk/work/cxx/interval.hpp

and many other things:

http://code.google.com/p/asadchev/source/browse/#svn%2Ftrunk%2Fprojects%2Fboost%2Fphoenix

0
Iakov Minochkin On

Don't look at Boost.Phoenix2.

Evolution of lambda expressions in boost looks like:

Bind -> Lambda, Phoenix2 (as Spirit part) -> Phoenix3 (as separate library, under development).

Result is single lambda-library with polymorphic functors support (others are going to become deprecated).