vous aide à écrire du code C++ concis et lisible.
Un bon code doit principalement être auto-documenté, mais en utilisant C++ en réalité, vous pouvez vous retrouver face à des éléments de bas niveau comme des itérateurs ou des boucles écrites à la main qui détournent l'attention de l'essence même de votre code.
FunctionalPlus est une petite bibliothèque d'en-tête uniquement qui vous aide à réduire le bruit du code et à gérer un seul niveau d'abstraction à la fois. En augmentant la brièveté et la maintenabilité de votre code, cela peut améliorer la productivité (et le plaisir !) à long terme. Il poursuit ces objectifs en fournissant des fonctions pures et faciles à utiliser qui vous libèrent de la mise en œuvre répétée des flux de contrôle couramment utilisés.
Supposons que vous ayez une liste de nombres et que vous ne vous intéressiez qu'aux impairs.
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 ...
}
Il existe différentes possibilités pour atteindre votre objectif. Certains d'entre eux sont :
Ints odds;
for ( int x : values)
{
if ( is_odd_int (x))
{
odds. push_back (x);
}
}
std::copy_if
depuis la STL Ints odds;
std::copy_if (std::begin(values), std::end(values),
std::back_inserter(odds), is_odd_int);
keep_if
de FunctionalPlus
auto odds = fplus::keep_if(is_odd_int, values);
Si vous pensez que la version 3 pourrait être la plus agréable à utiliser, vous aimerez peut-être FunctionalPlus. Et si vous pensez toujours que la boucle for écrite à la main est plus facile à comprendre, réfléchissez également à ce qui se passerait si le corps de la boucle (c'est-à-dire une fonction lambda correspondante dans l'appel à fplus::keep_if
) était beaucoup plus long. En lisant keep_if
vous saurez immédiatement que odds
ne peuvent contenir que des éléments provenant de values
et sélectionnés par un prédicat, éventuellement compliqué. Dans le cas de la boucle for, vous n'avez aucune idée de ce qui se passe jusqu'à ce que vous ayez lu l'intégralité du corps de la boucle. La version en boucle aurait probablement besoin d'un commentaire en haut indiquant ce que l'utilisation de keep_if
dirait à première vue.
Vous trouverez ci-dessous quelques courts exemples montrant de belles choses que vous pouvez faire avec des fonctions et des conteneurs à l'aide de FunctionalPlus.
Vous pouvez tester le contenu d'un conteneur pour diverses propriétés, par exemple
# 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
Il existe également des fonctions pratiques pour récupérer les propriétés des conteneurs. Par exemple, vous pouvez compter les occurrences d'un caractère dans une chaîne.
# 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;
}
Sortir:
There actually are this many 'I's in team: 2
Trouver l'élément le mieux noté dans un conteneur est très simple par rapport à une version écrite à la main (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;
}
Sortir:
Muffin is happy and sleepy. *purr* *purr* *purr*
Disons que la fonction suivante vous est donnée.
std::list< int > collatz_seq ( int x);
Et vous souhaitez créer un std::map
contenant des représentations sous forme de chaîne des séquences Collatz pour tous les nombres inférieurs à 30. Vous pouvez également implémenter cela de manière fonctionnelle.
# 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;
}
Sortir:
[13 => 40 => 20 => 10 => 5 => 16 => 8 => 4 => 2 => 1]
[17 => 52 => 26 => 13 => 40 => 20 => 10 => 5 => 16 => 8 => 4 => 2 => 1]
Les fonctions présentées fonctionnent non seulement avec les conteneurs STL par défaut comme std::vector
, std::list
, std::deque
, std::string
etc. mais également avec des conteneurs personnalisés fournissant une interface similaire.
FunctionalPlus en déduit les types pour vous lorsque cela est possible. Prenons une ligne de code de l'exemple Collatz :
auto show_collats_seq = fplus::compose(collatz_seq, show_ints);
collatz_seq
est une fonction prenant un uint64_t
et renvoyant un list
. show_ints
prend un list
et renvoie une string
. En utilisant function_traits
, écrit par kennyim, il est possible de déduire automatiquement l'expression fplus::compose(collatz_seq, show_ints)
comme étant une fonction prenant un uint64_t
et renvoyant une string
, vous n'avez donc pas besoin de fournir manuellement des indices de type à le compilateur.
Si deux fonctions dont les « types de connexion » ne correspondent pas sont transmises, un message d'erreur sans ambiguïté décrivant le problème sera généré. FunctionalPlus utilise des assertions au moment de la compilation pour éviter les messages d'erreur longs et déroutants que les compilateurs génèrent lorsqu'ils sont confrontés à des erreurs de type dans les modèles de fonction.
Changer la façon dont vous programmez de "écrire vos propres boucles et ifs imbriqués" à "composer et utiliser de petites fonctions" entraînera plus d'erreurs au moment de la compilation, mais sera payant en ayant moins d'erreurs au moment de l'exécution. De plus, des erreurs de compilation plus précises réduiront le temps passé au débogage.
L'article « Programmation fonctionnelle en C++ avec la bibliothèque FunctionalPlus ; aujourd'hui : HackerRank challenge Gemstones » propose une introduction fluide à la bibliothèque en montrant comment développer une solution élégante à un problème en utilisant l'approche FunctionalPlus.
Il existe également sur Udemy un cours « Programmation fonctionnelle utilisant C++ » qui utilise largement FunctionalPlus pour expliquer les concepts fonctionnels généraux.
Le tutoriel « Pierres précieuses » ci-dessus explique comment appliquer la pensée fonctionnelle pour arriver à la solution ci-dessous au problème suivant :
Recherchez le nombre de caractères présents dans chaque ligne d'un texte saisi.
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));
}
En utilisant la fonctionnalité de namespace fwd
, vous pouvez vous passer de variables temporaires et indiquer clairement que l'ensemble du processus pousse simplement l'entrée à travers une chaîne de fonctions, similaire au concept de tube dans la ligne de commande 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 ()
);
}
Dans fplus::fwd::
vous trouvez à nouveau de nombreuses fonctions fplus::
, mais dans une version partiellement curry, c'est-à-dire fplus::foo : (a, b, c) -> d
a son homologue avec fplus::foo : (a, b) -> (c -> d)
. Cela rend le style ci-dessus possible.
Alternativement à la version d'application avancée, vous pouvez également écrire sans points et définir votre fonction par composition :
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()
);
À propos, si vous avez besoin des paramètres d'une fonction binaire dans l'ordre inverse, namespace fplus::fwd::flip
existe également. fplus::bar : (a, b) -> c
n'a pas seulement son analogue dans fplus::fwd::bar : a -> b -> c
mais aussi dans fplus::fwd::flip::bar : b -> a -> c
.
Si vous recherchez une fonction FunctionalPlus spécifique dont vous ne connaissez pas encore le nom, vous pouvez bien sûr utiliser la fonction de saisie semi-automatique de votre IDE pour parcourir le contenu de l' namespace fplus
. Mais la méthode recommandée est d'utiliser le site Web de recherche de l'API FunctionalPlus . Vous pouvez effectuer une recherche rapide par mots-clés ou par signatures de types de fonctions. Si vous préférez, vous pouvez également parcourir le code source à l'aide de Sourcegraph.
Les fonctions de base sont rapides, grâce au concept d'abstraction sans surcharge du C++. Voici quelques mesures du premier exemple, prises sur un ordinateur de bureau standard, compilées avec GCC et le drapeau 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 |
Le compilateur semble donc faire un très bon travail en optimisant et en intégrant tout pour que les performances du code machine soient fondamentalement égales.
Les fonctions les plus complexes pourraient parfois être écrites de manière plus optimisée. Si vous utilisez FunctionalPlus dans un scénario critique en termes de performances et que le profilage montre que vous avez besoin d'une version plus rapide d'une fonction, veuillez me le faire savoir ou même aider à améliorer FunctionalPlus.
FunctionalPlus en interne peut souvent fonctionner sur place si un conteneur donné est une valeur r (par exemple dans des appels chaînés) et évite ainsi de nombreuses allocations et copies inutiles. Mais ce n’est pas le cas dans toutes les situations. Cependant, grâce au travail avec un langage multi-paradigmes, on peut facilement combiner du code impératif optimisé manuellement avec les fonctions fplus
. Heureusement, l'expérience (c'est-à-dire le profilage) montre que dans la plupart des cas, la grande majorité du code d'une application n'est pas pertinent pour les performances globales et la consommation de mémoire. Donc, se concentrer initialement sur la productivité des développeurs et la lisibilité du code est une bonne idée.
FunctionalPlus et range-v3 (base des ranges
en C++-20) ont des points communs, comme le montre l'extrait de code suivant.
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);
Il existe cependant quelques différences. Les plages Range-v3 sont paresseuses, ce qui signifie qu'aucune mémoire intermédiaire n'est allouée au cours des étapes uniques d'une chaîne de traitement comme celle ci-dessus. En revanche, lorsque vous utilisez FunctionalPlus, vous travaillez avec des conteneurs STL normaux. De plus, l'implémentation d'une nouvelle fonction est plus simple que l'écriture d'un nouvel adaptateur de plage. De plus, FunctionalPlus fournit beaucoup plus de fonctions prêtes à l'emploi et dispose du site Web de recherche API. Le choix entre les deux bibliothèques dépend donc de vos préférences et des besoins du projet.
Un compilateur compatible C++14 est nécessaire. Les compilateurs à partir de ces versions fonctionnent bien :
Des guides sur les différentes manières d’installer FunctionalPlus peuvent être trouvés dans INSTALL.md.
Les fonctionnalités de cette bibliothèque ont initialement augmenté en raison de mon besoin personnel lors de mon utilisation régulière du C++. Je fais de mon mieux pour le rendre sans erreur et aussi confortable à utiliser que possible. L'API pourrait encore changer à l'avenir. Si vous avez des suggestions, trouvez des erreurs, manquez certaines fonctions ou souhaitez faire part de commentaires/critiques généraux, j'aimerais avoir de vos nouvelles. Bien entendu, les contributions sont également les bienvenues.
Distribué sous la licence logicielle Boost, version 1.0. (Voir le fichier LICENSE
d'accompagnement ou une copie sur http://www.boost.org/LICENSE_1_0.txt)