Techniken in der Softwareentwicklung: Partial Function Application
Partial Function Application ist eine Technik, bei der eine Funktion einige ihrer Argumente bindet und eine Funktion zurückgibt, die weniger Argumente benötigt.
- Rainer Grimm
Partial Function Application oder auch Partial Application ist eine Technik in der Softwareentwicklung, die dem Currying ähnelt. Letzteres Verfahren wird vor allem gerne in funktionalen Sprachen verwendet.
Vor ein paar Wochen hatte ich eine Diskussion mit ein einigen meiner Blog-Leser. Ein Leser meinte, dass ich ĂĽber Partial Function Application schreiben sollte. Ein anderer Leser meinte, dass C++ keine Partial Function Application unterstĂĽtzt. Das ist falsch. C++ unterstĂĽtzt Partial Function Application in vielen Varianten. Die Varianten basieren auf std::function
, std::bind
, std::bind_front
, lambdas, auto
und currying.
Beginnen möchte ich mit ein wenig Theorie.
Currying
Partial Function Application ist einer Technik namens Currying sehr ähnlich. Currying ist ein bekanntes Idiom, das in funktionalen Sprachen wie Haskell gerne verwendet wird. Es steht für eine Technik, bei der eine Funktion, die mehr als ein Argument annimmt, nacheinander in eine Reihe von Funktionen umgewandelt wird, die nur ein Argument annehmen. Deshalb gibt es in einer Programmiersprache wie Haskell nur Funktionen mit einem Argument. Ich höre deine Frage: Wie ist es möglich, eine Funktion wie add
zu implementieren, die zwei Argumente braucht? Die Magie geschieht implizit. Eine Funktion, die n Argumente benötigt, wird in Funktionen umgewandelt, die eine Funktion zurückgibt, die nur n -1 Argumente benötigt. Das erste Element wird bei dieser Umwandlung ausgewertet.
Der Name Currying wurde von dem Mathematiker Haskell Curry und Moses Schönfinkel geprägt. Currying ist nach dem Familiennamen von Haskell Curry benannt; Haskell nach seinem Vornamen. Manchmal wird Currying auch Schönfinkeln genannt.
Partial Function Application ist mächtiger als Currying, weil man beliebige Funktionsargumente auswerten kannst. Seit C++11 unterstützt C++ std::function
und std::bind
.
std::bind
und std::function
std::bind
ermöglicht es, Callables (aufrufbare Einheiten) auf verschiedene Arten zu erstellen. Callables sind alle Entitäten, die sich wie eine Funktion verhalten. Das sind insbesondere Lambda-Ausdrücke, Funktionsobjekte oder Funktionen selbst.
Man kann
- Funktionsargumente an beliebige Positionen binden,
- die Reihenfolge der Funktionsargumente neu ordnen,
- Platzhalter fĂĽr Funktionsargumente einfĂĽhren,
- Funktionen teilweise auswerten.
AuĂźerdem
- das neue Callable direkt aufrufen,
- das Callable in einem Algorithmus der Standard Template Library (STL) verwenden,
- das Callable in
std::function
speichern.
Bevor ich ein Beispiel zeige, muss ich noch kurz std::function
vorstellen:
std::function
ist ein polymorpher Funktions-Wrapper. Er kann beliebige Callables annehmen und ihnen einen Namen geben.std::function
ist notwendig, wenn der Typ eines Callable angegeben werden muss.
Nun möchte ich ein erstes Beispiel für Partial Function Application vorstellen:
// bindAndFunction.cpp
#include <functional>
#include <iostream>
double divMe(double a, double b){
return double(a/b);
}
using namespace std::placeholders; // (1)
int main(){
std::cout << '\n';
// invoking the function object directly
std::cout << "1/2.0= " << std::bind(divMe, 1, 2.0)()
<< '\n'; // (2)
// placeholders for both arguments // (3)
std::function<double(double, double)>
myDivBindPlaceholder= std::bind(divMe, _1, _2);
std::cout << "1/2.0= " << myDivBindPlaceholder(1, 2.0) << '\n';
// placeholders for both arguments, swap the arguments (4)
std::function<double(double, double)>
myDivBindPlaceholderSwap= std::bind(divMe, _2, _1);
std::cout << "1/2.0= " << myDivBindPlaceholderSwap(2.0, 1)
<< '\n';
// placeholder for the first argument (5)
std::function<double(double)>
myDivBind1St= std::bind(divMe, _1, 2.0);
std::cout<< "1/2.0= " << myDivBind1St(1) << '\n';
// placeholder for the second argument (6)
std::function<double(double)>
myDivBind2Nd= std::bind(divMe, 1.0, _1);
std::cout << "1/2.0= " << myDivBind2Nd(2.0) << '\n';
std::cout << '\n';
}
Um die einfache Schreibweise _1
, _2
fĂĽr die Platzhalter std::placeholders::_1
, std::placeholders::_2
im Quellcode zu verwenden, muss ich den Namensraum std::placeholders
in Zeile 1 einfĂĽhren.
Ich binde in Zeile 2 im Ausdruck std::bind(divMe, 1, 2.0)
die Argumente 1 und 2.0 an die Funktion divMe
und rufe sie an Ort und Stelle auf. (3, 4, 5 und 6) folgen einer ähnlichen Strategie, aber ich gebe den erstellten Callables mit std::function
einen Namen und rufe sie schlieĂźlich auf. Eine Template-Signatur wie double(double, double)
(4) oder double(double)
(5 und 6) steht fĂĽr die Art der Callables, die std::function
akzeptiert. double(double, double)
ist ein Callable, das zwei double
annimmt und ein double
zurĂĽckgibt.
Insbesondere die letzten beiden Beispiele (5 und 6), in denen std::function
eine Funktion der Arität zwei annimmt und eine Funktion der Arität eins zurückgibt, sind ziemlich erstaunlich. Die Arität einer Funktion ist die Anzahl der Argumente, die sie erhält.std::bind
wertet in beiden Aufrufen nur ein Argument aus und verwendet fĂĽr das nicht ausgewertete einen Platzhalter. Diese Technik wird Partial Function Application genannt.
Zum Abschluss ist hier die Ausgabe des Programms:
In C++11 gibt es eine weitere Möglichkeit, Partial Function Application einzusetzen: Lambda-Ausdrücke.
Lambda AusdrĂĽcke
std::bind
und std::function
sind in C++11 nahezu ĂĽberflĂĽssig. Man kann Lambda-AusdrĂĽcke anstelle von std::bind
und auto
fast immer anstelle von std::function
verwenden. Hier ist das entsprechende Programm, das auf auto
und Lambda-AusdrĂĽcken basiert.
// lambdaAndAuto.cpp
#include <functional>
#include <iostream>
double divMe(double a, double b){
return double(a/b);
}
using namespace std::placeholders;
int main(){
std::cout << '\n';
// invoking the function object directly
std::cout << "1/2.0= " << [](int a, int b)
{ return divMe(a, b); }(1, 2.0) << '\n';
// placeholders for both arguments
auto myDivBindPlaceholder= [](int a, int b)
{ return divMe(a, b); };
std::cout << "1/2.0= " << myDivBindPlaceholder(1, 2.0) << '\n';
// placeholders for both arguments, swap the arguments
auto myDivBindPlaceholderSwap= [](int a, int b)
{ return divMe(b, a); };
std::cout << "1/2.0= " << myDivBindPlaceholderSwap(2.0, 1)
<< '\n';
// placeholder for the first argument
auto myDivBind1St= [](int a){ return divMe(a, 2.0); };
std::cout<< "1/2.0= " << myDivBind1St(1) << '\n';
// placeholder for the second argument
auto myDivBind2Nd= [](int b){ return divMe(1, b); };
std::cout << "1/2.0= " << myDivBind2Nd(2.0) << '\n';
std::cout << '\n';
}
Der Ausdruck [](int a, int b){ return divMe(a, b); }(1, 2.0) definiert einen Lambda-Ausdruck, der divMe
ausfĂĽhrt. Die nachgestellten geschweiften Klammern rufen den Lambda-Ausdruck nur mit den Argumenten 1 und 2.0 auf. Die ĂĽbrigen Lambda-AusdrĂĽcke werden dagegen in den nachfolgenden Zeilen aufgerufen. Dank eines Lambda-Ausdrucks kann jedes Argument der zugrunde liegenden Funktion gebunden werden.
Bis jetzt habe ich Partial Function Application mit std::bind
und Lambda-AusdrĂĽcken angewendet. In C++20 gibt es eine neue Variante von std::bind
:
std::bind_front
std::bind_front
erzeugt ein Callable. std::bind_front
kann eine beliebige Anzahl von Argumenten haben und bindet seine Argumente an der Front. Das wirft die Frage auf, warum wir std::bind_front
haben, denn seit C++11 gibt es std::bind
, das auch am Anfang binden kann. Der Grund ist einfach: Erstens ist std::bind_front
einfacher zu benutzen, weil es keine Platzhalter braucht, und zweitens propagiert std::bind_front
eine Ausnahmespezifikation des zugrunde liegenden Callables.
Das folgende Programm veranschaulicht, dass sich std::bind_front
durch std::bind
oder Lambda-Ausdrücke ersetzen lässt.
// bindFront.cpp
#include <functional>
#include <iostream>
int plusFunction(int a, int b) {
return a + b;
}
auto plusLambda = [](int a, int b) {
return a + b;
};
int main() {
std::cout << '\n';
auto twoThousandPlus1 =
std::bind_front(plusFunction, 2000); // (1)
std::cout << "twoThousandPlus1(20): "
<< twoThousandPlus1(20) << '\n';
auto twoThousandPlus2 =
std::bind_front(plusLambda, 2000); // (2)
std::cout << "twoThousandPlus2(20): "
<< twoThousandPlus2(20) << '\n';
auto twoThousandPlus3 =
std::bind_front(std::plus<int>(), 2000); // (3)
std::cout << "twoThousandPlus3(20): "
<< twoThousandPlus3(20) << '\n';
std::cout << "\n\n";
using namespace std::placeholders;
auto twoThousandPlus4 =
std::bind(plusFunction, 2000, _1); // (4)
std::cout << "twoThousandPlus4(20): "
<< twoThousandPlus4(20) << '\n';
auto twoThousandPlus5 = [](int b)
{ return plusLambda(2000, b); }; // (5)
std::cout << "twoThousandPlus5(20): "
<< twoThousandPlus5(20) << '\n';
std::cout << '\n';
}
Jeder Aufruf (1 - 5) erhält eine Callable mit zwei Argumenten und gibt eine Callable mit nur einem Argument zurück, weil das erste Argument an 2000 gebunden ist. Die Callable ist eine Funktion (1), ein Lambda-Ausdruck (2) und ein vordefiniertes Funktionsobjekt (3). _1
steht für das fehlende Argument. Mit dem Lambda-Ausdruck (5) lässt sich ein Argument direkt anwenden und ein Argument b
fĂĽr den fehlenden Parameter angeben. Aus Lesbarkeits-Sicht ist std::bind_front
deutlich angenehmer als std::bind
oder der Lambda-Ausdruck.
Wie geht's weiter?
Argument-Dependent Lookup (ADL), auch bekannt als Koening Lookup, steht für eine Reihe von "magischen" Regeln für das Auflösen von unqualifizierten Funktionen auf der Grundlage ihrer Funktionsargumente. (rme)