What are the benefits of using Boost.Phoenix?
Asked Answered
S

5

21

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?

Skidproof answered 16/2, 2011 at 7:6 Comment(1)
the author of the Boost.Spirit library regularly posts on SO.Chlo
B
14

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

Beetner answered 17/2, 2011 at 5:48 Comment(3)
Can you show an example piece of code that demonstrates the difference?Knickerbockers
Thanks, I appreciate it. I'm a little confused about two things though: If you're going through the trouble of making an entire struct is_odd_impl (instead of just saying boost::lambda::_1 % 2 == 1), then why aren't you just saying is_odd_impl is_odd; instead of boost::phoenix::function<is_odd_impl> is_odd;? What's the benefit of boost::phoenix::function here?Knickerbockers
Two things: 1) If you declare is_odd_impl is_odd you can not use it as part of a phoenix expression (i.e. _2*is_odd(_1*2.)). 2) The benefit of phoenix function over lambda bind is that that you can encapsulate (and specialize) an arbitrarily complex function. Imagine if you want a function that has a complicated loop and conditionals or whose actual behavior/implementation depends on the type of the argument (good luck implementing that as a pure Lambda expression.Beetner
A
5

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

Aminoplast answered 16/2, 2011 at 7:30 Comment(1)
Links seem to no longer be activeMotorboat
G
4

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.

Gurias answered 16/2, 2011 at 7:21 Comment(0)
D
3

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

Desjardins answered 28/2, 2011 at 17:28 Comment(0)
J
1

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

Jallier answered 16/2, 2011 at 7:13 Comment(6)
You're right, compilation time increases. But, IMHO, it's not real problem with modern computers...Skidproof
You obviously don't work on large projects. I work on a project that uses relatively little templates, yet it compiles for 20 minutes on an 8-core machine. Linking is also a big problem -- it can't be parallelized, and templates produce extremely long (and thus slow to process) symbol names.Jallier
@zvrba: Fortunately, we wouldn't be so greedy as to compromise the correctness and maintainability of our code, which has lasting effects over the many times the application will be run and maintained, because we don't want to be patient about a single compile.Superpatriot
@GMan: Oh, that was a bold claim. Just using boost will magically make the program correct and maintainable?Jallier
@zvrba: Withholding the use of templates where templates are the appropriate solution is the premise of my argument, and is what I picked up from your comments.Superpatriot
@GMan: I did not suggest to withhold the use of templates. I said that using boost in itself (or, more correctly, heavy template metaprogramming) can cause a maintenance headache because of long compilation times, long link times, horrible error messages and horrible debugging experience of deep stack traces.Jallier

© 2022 - 2024 — McMap. All rights reserved.