Каковы преимущества использования Boost.Phoenix?

Не могу понять, в чем реальная польза от использования Boost.Phoenix.

Когда я использую его с грамматиками Boost.Spirit, это действительно полезно:

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

Когда я использую его для лямбда-функций, это также полезно и элегантно:

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

Но каковы преимущества всего остального в этой библиотеке? В документации сказано: "Функторы везде". Я не понимаю, что в этом хорошего?


person Denis Shevchenko    schedule 16.02.2011    source источник
comment
автор библиотеки Boost.Spirit регулярно публикует сообщения на SO.   -  person Sam Miller    schedule 17.02.2011


Ответы (5)


Я укажу вам, в чем критическая разница между Boost.Lambda и Boost.Phoenix:

Boost.Phoenix поддерживает (статически) полиморфные функторы, в то время как привязки Boost.Lambda всегда мономорфны.

(В то же время во многих аспектах две библиотеки могут быть объединены, поэтому они не являются исключительным выбором.)

Позвольте мне проиллюстрировать (Предупреждение: код не проверен.):

Феникс

В Phoenix функтор может быть преобразован в «ленивую функцию» Phoenix (из 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_odd_impl). То есть is_odd(_1) может воздействовать на что угодно (в этом есть смысл). Например в is_odd(_1)(2u)==true и is_odd(_1)(2l)==true. is_odd можно объединить в более сложное выражение без потери полиморфного поведения.

Лямбда-попытка

Что ближе всего к этому можно получить в Boost.Lambda? Мы можем определить две перегрузки:

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

но для создания «ленивой функции» Lambda нам нужно будет выбрать один из двух:

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

Даже если мы определим версию шаблона

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

нам придется привязываться к конкретному экземпляру функции шаблона, например

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

Ни f1, ни f2, ни f3 не являются действительно полиморфными, поскольку выбор был сделан во время связывания.

(Примечание 1: это может быть не лучший пример, поскольку может показаться, что все работает из-за неявных преобразований из unsigned в long, но это другой вопрос.)

Подводя итог, полиморфная функция/функтор Lambda не может связываться с полиморфной функцией (насколько мне известно), а Phoenix может. Это правда, что Phoenix полагается на «Результат протокола» http://www.boost.org/doc/libs/1_54_0/libs/utility/utility.htm#result_of, но 1) по крайней мере это возможно, 2) это не проблема в C+ +11, где типы возвращаемых значений очень легко вывести, и это можно сделать автоматически.

Фактически в C++11 лямбда-выражения Phoenix по-прежнему более эффективны, чем встроенные лямбда-выражения C++11. Даже в C++14, где реализованы универсальные лямбда-выражения template, Phoenix по-прежнему более универсален, поскольку допускает определенный уровень самоанализа. (В этом и в остальном Джоэл де Гусман (разработчик Phoenix) был и остается далеко впереди своего времени.)

person alfC    schedule 17.02.2011
comment
Можете ли вы показать пример кода, демонстрирующий разницу? - person user541686; 07.11.2013
comment
Спасибо, я ценю это. Однако я немного запутался в двух вещах: если вы пытаетесь составить целое struct is_odd_impl (вместо того, чтобы просто сказать boost::lambda::_1 % 2 == 1), то почему вы просто не говорите is_odd_impl is_odd; вместо boost::phoenix::function<is_odd_impl> is_odd;? Какая польза от boost::phoenix::function здесь? - person user541686; 07.11.2013
comment
Две вещи: 1) Если вы объявите is_odd_impl is_odd, вы не сможете использовать его как часть выражения феникса (т.е. _2*is_odd(_1*2.)). 2) Преимущество функции phoenix по сравнению с лямбда-привязкой заключается в том, что вы можете инкапсулировать (и специализировать) произвольно сложную функцию. Представьте, что вам нужна функция со сложным циклом и условными выражениями или чье фактическое поведение/реализация зависит от типа аргумента (удачи в реализации этого как чистого лямбда-выражения. - person alfC; 07.11.2013

Ну, это очень мощный лямбда-язык.

Я использовал его для создания прототипа математической DSL:

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

и многое другое:

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

person Anycorn    schedule 16.02.2011
comment
Ссылки похоже больше не активны - person Alex C; 20.10.2016

Фениксом никогда не пользовался, но...

Из документов библиотеки Phoenix:

Библиотека Phoenix позволяет использовать методы FP, такие как функции высшего порядка, лямбда (безымянные функции), каррирование (частичное применение функций) и ленивые вычисления в C++.

Из статьи Википедии о функциональном программировании:

... функциональное программирование - это парадигма программирования, которая рассматривает вычисления как оценку математических функций и избегает состояния и изменяемых данных. Он делает упор на применение функций, в отличие от императивного стиля программирования, который делает упор на изменения состояния.

Итак, Phoenix — это библиотека для включения функционального программирования на C++.

Основной интерес к функциональному программированию в наши дни, по-видимому, связан с предполагаемыми преимуществами в правильности и производительности из-за ограничения или устранения побочных эффектов.

Правильность, потому что без побочных эффектов код, который вы видите, это все, что происходит в системе. Какой-то другой код не изменит ваше состояние под вами. В такой среде гораздо легче писать безошибочный код.

Производительность, поскольку без побочных эффектов код, который вы пишете, может безопасно выполняться параллельно, без каких-либо примитивов управления ресурсами или трюков с атомарным доступом. Многопоточность может быть включена очень легко, даже автоматически, и работает очень эффективно.

person Merlyn Morgan-Graham    schedule 16.02.2011
comment
Большое спасибо за ответ! - person Denis Shevchenko; 16.02.2011

Не смотрите на Boost.Phoenix2.

Эволюция лямбда-выражений в boost выглядит так:

Bind -> Lambda, Phoenix2 (как часть Spirit) -> Phoenix3 (как отдельная библиотека, в разработке).

Результатом является единая лямбда-библиотека с поддержкой полиморфных функторов (другие станут устаревшими).

person Iakov Minochkin    schedule 28.02.2011

Функциональное программирование на С++. Это трудно объяснить, если вы ранее не использовали язык с надлежащей поддержкой функционального программирования, такой как SML. Я попытался использовать Phoenix и нашел его хорошим, но очень непрактичным в реальных проектах, потому что он значительно увеличивает время компиляции, а сообщения об ошибках ужасны, когда вы делаете что-то не так. Я помню, как получил несколько мегабайт ошибок от GCC, когда играл с Phoenix. Кроме того, отладка экземпляров глубоко вложенных шаблонов — это PITA. (На самом деле, это также все аргументы против использования большей части наддува.)

person zvrba    schedule 16.02.2011
comment
Вы правы, время компиляции увеличивается. Но, ИМХО, с современными компьютерами это не проблема... - person Denis Shevchenko; 16.02.2011
comment
Вы явно не работаете над большими проектами. Я работаю над проектом, который использует относительно мало шаблонов, но компилируется за 20 минут на 8-ядерной машине. Связывание также является большой проблемой — его нельзя распараллелить, а шаблоны создают очень длинные (и, следовательно, медленные в обработке) имена символов. - person zvrba; 16.02.2011
comment
@zvrba: К счастью, мы не были бы настолько жадными, чтобы поставить под угрозу правильность и удобство сопровождения нашего кода, что имеет долгосрочные последствия в течение многих раз, когда приложение будет запускаться и поддерживаться, потому что мы не хотим быть терпеливыми в отношении одиночная компиляция. - person GManNickG; 16.02.2011
comment
@GMan: О, это было смелое заявление. Простое использование boost волшебным образом сделает программу корректной и удобной в сопровождении? - person zvrba; 16.02.2011
comment
@zvrba: Отказ от использования шаблонов, где шаблоны являются подходящим решением, является предпосылкой моего аргумента, и это то, что я понял из ваших комментариев. - person GManNickG; 16.02.2011
comment
@GMan: я не предлагал отказаться от использования шаблонов. Я сказал, что использование boost само по себе (или, правильнее сказать, тяжелое метапрограммирование шаблонов) может вызвать головную боль при обслуживании из-за длительного времени компиляции, длительного времени компоновки, ужасных сообщений об ошибках и ужасного опыта отладки глубоких трассировок стека. - person zvrba; 16.02.2011