hilft Ihnen, prägnanten und lesbaren C++-Code zu schreiben.
Großartiger Code sollte größtenteils selbstdokumentierend sein, aber während Sie C++ in der Realität verwenden, kann es passieren, dass Sie mit einfachen Dingen wie Iteratoren oder handgeschriebenen Schleifen zu tun haben, die vom eigentlichen Wesen Ihres Codes ablenken.
FunctionalPlus ist eine kleine reine Header-Bibliothek, die Sie dabei unterstützt, Coderauschen zu reduzieren und jeweils nur eine einzige Abstraktionsebene zu bewältigen. Durch die Erhöhung der Kürze und Wartbarkeit Ihres Codes kann die Produktivität (und der Spaß!) auf lange Sicht verbessert werden. Es verfolgt diese Ziele, indem es reine und benutzerfreundliche Funktionen bereitstellt, die Sie davon befreien, häufig verwendete Kontrollabläufe immer wieder zu implementieren.
Angenommen, Sie haben eine Liste mit Zahlen und interessieren sich nur für die ungeraden Zahlen.
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 ...
}
Es gibt verschiedene Möglichkeiten, Ihr Ziel zu erreichen. Einige davon sind:
Ints odds;
for ( int x : values)
{
if ( is_odd_int (x))
{
odds. push_back (x);
}
}
std::copy_if
aus der STL Ints odds;
std::copy_if (std::begin(values), std::end(values),
std::back_inserter(odds), is_odd_int);
keep_if
von FunctionalPlus
auto odds = fplus::keep_if(is_odd_int, values);
Wenn Sie der Meinung sind, dass die Arbeit mit Version 3 am angenehmsten ist, könnte Ihnen FunctionalPlus gefallen. Und wenn Sie immer noch der Meinung sind, dass die handgeschriebene for-Schleife einfacher zu verstehen ist, überlegen Sie auch, was passieren würde, wenn der Schleifenkörper (dh eine entsprechende Lambda-Funktion im Aufruf von fplus::keep_if
) viel länger wäre. Wenn Sie keep_if
lesen, wissen Sie immer noch sofort, dass odds
nur Elemente enthalten können, die aus values
stammen und durch ein möglicherweise kompliziertes Prädikat ausgewählt wurden. Im Fall einer for-Schleife haben Sie keine Ahnung, was passiert, bis Sie den gesamten Schleifenkörper gelesen haben. Die Schleifenversion bräuchte wahrscheinlich oben einen Kommentar, der angibt, was die Verwendung von keep_if
auf den ersten Blick verrät.
Nachfolgend finden Sie einige kurze Beispiele, die schöne Dinge zeigen, die Sie mit FunctionalPlus mit Funktionen und Containern machen können.
Sie können den Inhalt eines Containers auf verschiedene Eigenschaften testen, z
# 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
Es gibt auch einige praktische Funktionen zum Abrufen der Eigenschaften von Containern. Sie können beispielsweise die Vorkommen eines Zeichens in einer Zeichenfolge zählen.
# 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;
}
Ausgabe:
There actually are this many 'I's in team: 2
Das Auffinden des am höchsten bewerteten Elements in einem Container ist im Vergleich zu einer handgeschriebenen Version sehr einfach (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;
}
Ausgabe:
Muffin is happy and sleepy. *purr* *purr* *purr*
Nehmen wir an, Sie haben die folgende Funktion angegeben.
std::list< int > collatz_seq ( int x);
Und Sie möchten eine std::map
erstellen, die String-Darstellungen der Collatz-Sequenzen für alle Zahlen unter 30 enthält. Sie können dies auch gut auf funktionale Weise implementieren.
# 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;
}
Ausgabe:
[13 => 40 => 20 => 10 => 5 => 16 => 8 => 4 => 2 => 1]
[17 => 52 => 26 => 13 => 40 => 20 => 10 => 5 => 16 => 8 => 4 => 2 => 1]
Die gezeigten Funktionen funktionieren nicht nur mit Standard-STL-Containern wie std::vector
, std::list
, std::deque
, std::string
usw., sondern auch mit benutzerdefinierten Containern, die eine ähnliche Schnittstelle bereitstellen.
FunctionalPlus leitet nach Möglichkeit Typen für Sie ab. Nehmen wir eine Codezeile aus dem Collatz-Beispiel:
auto show_collats_seq = fplus::compose(collatz_seq, show_ints);
collatz_seq
ist eine Funktion, die ein uint64_t
annimmt und ein list
zurückgibt. show_ints
nimmt eine list
und gibt einen string
zurück. Durch die Verwendung von function_traits
, geschrieben von kennyim, ist es möglich, den Ausdruck fplus::compose(collatz_seq, show_ints)
automatisch als eine Funktion abzuleiten, die ein uint64_t
annimmt und einen string
zurückgibt, sodass Sie nicht manuell Typhinweise bereitstellen müssen der Compiler.
Wenn zwei Funktionen übergeben werden, deren „Verbindungstypen“ nicht übereinstimmen, wird eine eindeutige Fehlermeldung generiert, die das Problem beschreibt. FunctionalPlus verwendet Assertionen zur Kompilierungszeit, um die verwirrend langen Fehlermeldungen zu vermeiden, die Compiler generieren, wenn sie mit Typfehlern in Funktionsvorlagen konfrontiert werden.
Wenn Sie die Art und Weise, wie Sie programmieren, von „Schreiben eigener Schleifen und verschachtelter Ifs“ auf „Komponieren und Verwenden kleiner Funktionen“ umstellen, führt dies zu mehr Fehlern zur Kompilierungszeit, zahlt sich jedoch dadurch aus, dass zur Laufzeit weniger Fehler auftreten. Darüber hinaus reduzieren präzisere Fehler bei der Kompilierung den Zeitaufwand für das Debuggen.
Der Artikel „Funktionale Programmierung in C++ mit der FunctionalPlus-Bibliothek; heute: HackerRank Challenge Gemstones“ bietet einen reibungslosen Einstieg in die Bibliothek, indem er zeigt, wie man mit dem FunctionalPlus-Ansatz eine elegante Lösung für ein Problem entwickeln kann.
Außerdem gibt es auf Udemy einen Kurs „Funktionale Programmierung mit C++“, der FunctionalPlus intensiv nutzt, um allgemeine funktionale Konzepte zu erklären.
Das obige Tutorial „Edelsteine“ erklärt, wie man funktionales Denken anwenden kann, um zur folgenden Lösung für das folgende Problem zu gelangen:
Ermitteln Sie die Anzahl der Zeichen, die in jeder Zeile eines Eingabetextes vorhanden sind.
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));
}
Durch die Verwendung der Funktionalität aus namespace fwd
können Sie ohne temporäre Variablen auskommen und deutlich machen, dass der gesamte Prozess lediglich die Eingabe durch eine Kette von Funktionen schiebt, ähnlich dem Pipe-Konzept in der Unix-Befehlszeile.
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 ()
);
}
In fplus::fwd::
findet man viele fplus::
Funktionen wieder, allerdings in einer teilweise gecurryten Version, d. h. fplus::foo : (a, b, c) -> d
hat sein Gegenstück mit fplus::foo : (a, b) -> (c -> d)
. Dies ermöglicht den oben genannten Stil.
Alternativ zur Vorwärtsanwendungsversion können Sie auch punktfrei schreiben und Ihre Funktion durch Zusammensetzung definieren:
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()
);
Für den Fall, dass Sie die Parameter einer Binärfunktion in umgekehrter Reihenfolge benötigen, gibt es übrigens auch namespace fplus::fwd::flip
. fplus::bar : (a, b) -> c
hat sein Analogon nicht nur in fplus::fwd::bar : a -> b -> c
, sondern auch in fplus::fwd::flip::bar : b -> a -> c
.
Wenn Sie nach einer bestimmten FunctionalPlus-Funktion suchen, deren Namen Sie noch nicht kennen, können Sie natürlich die Autovervollständigungsfunktion Ihrer IDE verwenden, um den Inhalt des namespace fplus
zu durchsuchen. Die empfohlene Methode ist jedoch die Verwendung der FunctionalPlus-API-Suchwebsite . Sie können damit schnell nach Schlüsselwörtern oder Funktionstypsignaturen suchen. Wenn Sie möchten, können Sie den Quellcode auch mit Sourcegraph durchsuchen.
Dank des Abstraktionskonzepts von C++ ohne Overhead sind die Grundfunktionen schnell. Hier sind einige Messungen des ersten Beispiels, aufgenommen auf einem Standard-Desktop-PC, kompiliert mit GCC und der O3
-Flagge.
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 |
Der Compiler scheint also sehr gute Arbeit zu leisten, indem er alles so optimiert und inlining, dass es in Bezug auf die Leistung des Maschinencodes im Wesentlichen gleich ist.
Die komplexeren Funktionen könnten jedoch manchmal optimierter geschrieben werden. Wenn Sie FunctionalPlus in einem leistungskritischen Szenario verwenden und die Profilerstellung zeigt, dass Sie eine schnellere Version einer Funktion benötigen, lassen Sie es mich bitte wissen oder helfen Sie sogar bei der Verbesserung von FunctionalPlus.
FunctionalPlus kann intern oft direkt vor Ort arbeiten, wenn ein bestimmter Container ein R-Wert ist (z. B. in verketteten Aufrufen) und vermeidet so viele unnötige Zuweisungen und Kopien. Dies ist jedoch nicht in allen Situationen der Fall. Dank der Arbeit mit einer Multi-Paradigmen-Sprache kann man jedoch leicht manuell optimierten imperativen Code mit fplus
-Funktionen kombinieren. Glücklicherweise zeigt die Erfahrung (auch bekannt als Profiling), dass in den meisten Fällen der Großteil des Codes in einer Anwendung für die Gesamtleistung und den Speicherverbrauch nicht relevant ist. Daher ist es eine gute Idee, sich zunächst auf die Produktivität der Entwickler und die Lesbarkeit des Codes zu konzentrieren.
FunctionalPlus und range-v3 (Basis für ranges
in C++-20) haben einige Gemeinsamkeiten, wie der folgende Codeausschnitt zeigt.
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);
Es gibt jedoch einige Unterschiede. Range-v3-Bereiche sind lazy, was bedeutet, dass während der einzelnen Schritte einer Verarbeitungskette wie oben kein Zwischenspeicher zugewiesen wird. Bei FunctionalPlus hingegen arbeiten Sie mit normalen STL-Containern. Auch die Implementierung einer neuen Funktion ist einfacher als das Schreiben eines neuen Bereichsadapters. Darüber hinaus bietet FunctionalPlus viel mehr sofort einsatzbereite Funktionen und verfügt über die API-Suchwebsite. Die Wahl zwischen den beiden Bibliotheken hängt also von Ihren Vorlieben und den Anforderungen des Projekts ab.
Es wird ein C++14- kompatibler Compiler benötigt. Compiler ab diesen Versionen sind in Ordnung:
Anleitungen für verschiedene Möglichkeiten zur Installation von FunctionalPlus finden Sie in INSTALL.md.
Die Funktionalität dieser Bibliothek wuchs zunächst aufgrund meines persönlichen Bedarfs bei regelmäßiger Verwendung von C++. Ich versuche mein Bestes, um es fehlerfrei und so komfortabel wie möglich zu gestalten. Die API könnte sich in Zukunft noch ändern. Wenn Sie Anregungen haben, Fehler finden, Funktionen vermissen oder allgemeines Feedback/Kritik abgeben möchten, freue ich mich über Ihre Nachricht. Natürlich sind auch Beiträge herzlich willkommen.
Verteilt unter der Boost-Softwarelizenz, Version 1.0. (Siehe Begleitdatei LICENSE
oder Kopie unter http://www.boost.org/LICENSE_1_0.txt)