помогает вам писать краткий и читаемый код C++.
Отличный код в основном должен быть самодокументируемым, но при использовании C++ в реальности вы можете столкнуться с низкоуровневыми вещами, такими как итераторы или рукописные циклы, которые отвлекают от реальной сути вашего кода.
FunctionalPlus — это небольшая библиотека, предназначенная только для заголовков, которая помогает уменьшить шум кода и работать только с одним уровнем абстракции одновременно. Повышая краткость и удобство сопровождения вашего кода, это может повысить производительность (и удовольствие!) в долгосрочной перспективе. Он преследует эти цели, предоставляя чистые и простые в использовании функции, которые освобождают вас от необходимости снова и снова реализовывать часто используемые потоки управления.
Предположим, у вас есть список чисел, и вас интересуют только нечетные числа.
bool is_odd_int ( int x) { return x % 2 != 0 ; }
int main ()
{
typedef vector< int > Ints;
Ints values = { 24 , 11 , 65 , 44 , 80 , 18 , 73 , 90 , 69 , 18 };
// todo: get odd numbers from values ...
}
Существуют разные возможности достижения вашей цели. Некоторые из них:
Ints odds;
for ( int x : values)
{
if ( is_odd_int (x))
{
odds. push_back (x);
}
}
std::copy_if
из STL Ints odds;
std::copy_if (std::begin(values), std::end(values),
std::back_inserter(odds), is_odd_int);
keep_if
из FunctionalPlus
auto odds = fplus::keep_if(is_odd_int, values);
Если вы считаете, что работать с версией 3 будет приятнее всего, вам может понравиться FunctionalPlus. И если вы все еще считаете, что цикл for, написанный вручную, легче понять, подумайте также, что произойдет, если тело цикла (т. е. соответствующая лямбда-функция в вызове fplus::keep_if
) будет намного длиннее. При чтении keep_if
вы все равно сразу поймете, что odds
могут содержать только элементы, полученные из values
и выбранные каким-либо, возможно, сложным предикатом. В случае цикла for вы понятия не имеете, что происходит, пока не прочтете все тело цикла. Версия цикла, вероятно, потребует комментария вверху, описывающего, что на первый взгляд покажет использование keep_if
.
Ниже приведены несколько коротких примеров, показывающих интересные вещи, которые можно делать с функциями и контейнерами с помощью FunctionalPlus.
Вы можете проверить содержимое контейнера на предмет различных свойств, например
# include < fplus/fplus.hpp >
# include < iostream >
int main ()
{
std::list things = { " same old " , " same old " };
if ( fplus::all_the_same (things))
std::cout << " All things being equal. " << std::endl;
}
team
Также есть несколько удобных функций для получения свойств контейнеров. Например, вы можете подсчитать количество вхождений символа в строку.
# include < fplus/fplus.hpp >
# include < iostream >
int main ()
{
std::string team = " Our team is great. I love everybody I work with. " ;
std::cout << " There actually are this many 'I's in team: " <<
fplus::count ( " I " , fplus::split_words ( false , team)) << std::endl;
}
Выход:
There actually are this many 'I's in team: 2
Найти элемент с самым высоким рейтингом в контейнере очень просто по сравнению с рукописной версией (1, 2).
# include < fplus/fplus.hpp >
# include < iostream >
struct cat
{
double cuteness () const
{
return softness_ * temperature_ * roundness_ * fur_amount_ - size_;
}
std::string name_;
double softness_;
double temperature_;
double size_;
double roundness_;
double fur_amount_;
};
void main ()
{
std::vector cats = {
{ " Tigger " , 5 , 5 , 5 , 5 , 5 },
{ " Simba " , 2 , 9 , 9 , 2 , 7 },
{ " Muffin " , 9 , 4 , 2 , 8 , 6 },
{ " Garfield " , 6 , 5 , 7 , 9 , 5 }};
auto cutest_cat = fplus::maximum_on ( std::mem_fn (&cat::cuteness), cats);
std::cout << cutest_cat. name_ <<
" is happy and sleepy. *purr* *purr* *purr* " << std::endl;
}
Выход:
Muffin is happy and sleepy. *purr* *purr* *purr*
Допустим, вам дана следующая функция.
std::list< int > collatz_seq ( int x);
И вы хотите создать std::map
содержащий строковые представления последовательностей Коллатца для всех чисел ниже 30. Вы можете реализовать это и функционально.
# include < fplus/fplus.hpp >
# include < iostream >
// std::list collatz_seq(std::uint64_t x) { ... }
int main ()
{
typedef std::list< int > Ints;
// [1, 2, 3 ... 29]
auto xs = fplus::numbers( 1 , 30 );
// A function that does [1, 2, 3, 4, 5] -> "[1 => 2 => 3 => 4 => 5]"
auto show_ints = fplus::bind_1st_of_2 (fplus::show_cont_with, " => " );
// A composed function that calculates a Collatz sequence and shows it.
auto show_collats_seq = fplus::compose (collatz_seq, show_ints);
// Associate the numbers with the string representation of their sequences.
auto collatz_dict = fplus::create_map_with (show_collats_seq, xs);
// Print some of the sequences.
std::cout << collatz_dict[ 13 ] << std::endl;
std::cout << collatz_dict[ 17 ] << std::endl;
}
Выход:
[13 => 40 => 20 => 10 => 5 => 16 => 8 => 4 => 2 => 1]
[17 => 52 => 26 => 13 => 40 => 20 => 10 => 5 => 16 => 8 => 4 => 2 => 1]
Показанные функции работают не только с контейнерами STL по умолчанию, такими как std::vector
, std::list
, std::deque
, std::string
и т. д., но также и с пользовательскими контейнерами, предоставляющими аналогичный интерфейс.
FunctionalPlus выводит типы за вас, где это возможно. Давайте возьмем одну строку кода из примера Коллатца:
auto show_collats_seq = fplus::compose(collatz_seq, show_ints);
collatz_seq
— это функция, принимающая uint64_t
и возвращающая list
. show_ints
принимает list
и возвращает string
. Используя function_traits
, написанную kennyim, можно автоматически вывести выражение fplus::compose(collatz_seq, show_ints)
как функцию, принимающую uint64_t
и возвращающую string
, поэтому вам не придется вручную предоставлять подсказки типа для компилятор.
Если переданы две функции, чьи «типы подключения» не совпадают, будет сгенерировано однозначное сообщение об ошибке, описывающее проблему. FunctionalPlus использует утверждения времени компиляции, чтобы избежать запутанно длинных сообщений об ошибках, которые компиляторы генерируют при обнаружении ошибок типа в шаблонах функций.
Изменение способа программирования с «написания собственных циклов и вложенных if» на «составление и использование небольших функций» приведет к большему количеству ошибок во время компиляции, но окупится меньшим количеством ошибок во время выполнения. Кроме того, более точные ошибки времени компиляции сократят время, затрачиваемое на отладку.
Статья «Функциональное программирование на C++ с помощью библиотеки FunctionalPlus; сегодня: HackerRank бросает вызов Gemstones» обеспечивает плавное введение в библиотеку, показывая, как можно разработать элегантное решение проблемы, используя подход FunctionalPlus.
Также на Udemy есть курс «Функциональное программирование с использованием C++», в котором для объяснения общих функциональных концепций широко используется FunctionalPlus.
В приведенном выше уроке «Драгоценные камни» объясняется, как можно применить функциональное мышление, чтобы прийти к приведенному ниже решению следующей проблемы:
Найдите количество символов, присутствующих в каждой строке входного текста.
std::string gemstone_count ( const std::string& input)
{
using namespace fplus ;
typedef std::set characters;
const auto lines = split_lines ( false , input); // false = no empty lines
const auto sets = transform (
convert_container,
lines);
// Build the intersection of all given character sets (one per line).
const auto gem_elements = fold_left_1 (
set_intersection, sets);
return show ( size_of_cont (gem_elements));
}
Используя функциональность namespace fwd
, вы можете обойтись без временных переменных и дать понять, что весь процесс просто передает ввод через цепочку функций, аналогично концепции канала в командной строке Unix.
std::string gemstone_count_fwd_apply ( const std::string& input)
{
using namespace fplus ;
typedef std::set characters;
return fwd::apply (
input
, fwd::split_lines ( false )
, fwd::transform (convert_container)
, fwd::fold_left_1 (set_intersection)
, fwd::size_of_cont ()
, fwd::show ()
);
}
В fplus::fwd::
вы снова найдете множество функций fplus::
, но в частично каррированной версии, т.е. fplus::foo : (a, b, c) -> d
имеет аналог с fplus::foo : (a, b) -> (c -> d)
. Это делает возможным описанный выше стиль.
В качестве альтернативы прямой версии приложения вы также можете написать без точек и определить свою функцию по композиции:
using namespace fplus ;
typedef std::set characters;
const auto gemstone_count_fwd_compose = fwd::compose(
fwd::split_lines ( false ),
fwd::transform(convert_container),
fwd::fold_left_1(set_intersection),
fwd::size_of_cont(),
fwd::show()
);
Кстати, если вам нужны параметры бинарной функции в обратном порядке, также существует namespace fplus::fwd::flip
. fplus::bar : (a, b) -> c
имеет аналог не только в fplus::fwd::bar : a -> b -> c
, но и в fplus::fwd::flip::bar : b -> a -> c
.
Если вы ищете конкретную функцию FunctionalPlus, имя которой вам еще не известно, вы, конечно, можете использовать функцию автозаполнения вашей IDE для просмотра содержимого пространства namespace fplus
. Но рекомендуемый способ — использовать поисковый сайт FunctionalPlus API . С его помощью вы можете быстро выполнять поиск по ключевым словам или сигнатурам типов функций. При желании вы также можете просмотреть исходный код с помощью Sourcegraph.
Базовые функции выполняются быстро благодаря концепции абстракции C++ без дополнительных затрат. Вот некоторые измерения из первого примера, снятые на стандартном настольном ПК, скомпилированные с помощью GCC и флага O3
.
5000 random numbers, keep odd ones, 20000 consecutive runs accumulated
----------------------------------------------------------------------
| Hand-written for loop | std::copy_if | fplus::keep_if |
|-----------------------|--------------|----------------|
| 0.632 s | 0.641 s | 0.627 s |
Таким образом, компилятор, похоже, проделывает очень хорошую работу по оптимизации и встраиванию всего, чтобы практически сравнять производительность машинного кода.
Однако более сложные функции иногда можно написать более оптимизированным способом. Если вы используете FunctionalPlus в сценарии, где производительность критична, и профилирование показывает, что вам нужна более быстрая версия функции, дайте мне знать или даже помогите улучшить FunctionalPlus.
Внутри FunctionalPlus часто может работать на месте, если данный контейнер является r-значением (например, в цепочке вызовов), и таким образом позволяет избежать множества ненужных выделений и копий. Но это не так во всех ситуациях. Однако благодаря работе с мультипарадигмальным языком можно легко комбинировать оптимизированный вручную императивный код с функциями fplus
. К счастью, опыт (он же профилирование) показывает, что в большинстве случаев подавляющее большинство кода приложения не влияет на общую производительность и потребление памяти. Поэтому изначально хорошей идеей будет сосредоточиться на продуктивности разработчиков и читаемости кода.
FunctionalPlus и range-v3 (основа ranges
в C++-20) имеют много общего, как показано в следующем фрагменте кода.
const auto times_3 = []( int i){ return 3 * i;};
const auto is_odd_int = []( int i){ return i % 2 != 0 ;};
const auto as_string_length = []( int i){ return std::to_string (i). size ();};
// FunctionalPlus
using namespace fplus ;
const auto result_fplus = fwd::apply(
numbers ( 0 , 15000000 )
, fwd::transform(times_3)
, fwd::drop_if(is_odd_int)
, fwd::transform(as_string_length)
, fwd::sum());
// range-v3
const auto result_range_v3 =
accumulate (
views::ints ( 0 , ranges::unreachable)
| views::take( 15000000 )
| views::transform(times_3)
| views::remove_if(is_odd_int)
| views::transform(as_string_length), 0);
Однако есть некоторые различия. Диапазоны Range-v3 являются ленивыми, что означает, что на отдельных этапах цепочки обработки, подобной приведенной выше, не выделяется промежуточная память. С другой стороны, при использовании FunctionalPlus вы работаете с обычными контейнерами STL. Кроме того, реализация новой функции проще по сравнению с написанием нового адаптера диапазона. Кроме того, FunctionalPlus предоставляет гораздо больше функций из коробки и имеет веб-сайт поиска API. Так что выбор между двумя библиотеками зависит от ваших предпочтений и потребностей проекта.
Требуется компилятор, совместимый с C++14 . Компиляторы из этих версий в порядке:
Руководства по различным способам установки FunctionalPlus можно найти в INSTALL.md.
Функциональность этой библиотеки изначально выросла из-за моей личной потребности в ней при регулярном использовании C++. Я изо всех сил стараюсь сделать его безошибочным и максимально удобным в использовании. API все еще может измениться в будущем. Если у вас есть какие-либо предложения, вы нашли ошибки, упустили некоторые функции или хотите высказать общий отзыв/критику, я буду рад услышать ваше мнение. Конечно, вклады также очень приветствуются.
Распространяется по лицензии Boost Software License, версия 1.0. (См. прилагаемый файл LICENSE
или копию по адресу http://www.boost.org/LICENSE_1_0.txt)