Funktionale Programmierung ist ein Stil der Programmentwicklung, bei dem einige spezifische Merkmale für die Arbeit mit Funktionen weit verbreitet sind. Es geht insbesondere darum, Funktionen als Argumente auf andere Funktionen zu übertragen und Funktionen von anderen Funktionen zurückzugeben. Das Konzept der „reinen Funktionen“ gehört auch zum funktionalen Programmierstil. Die Ausgabe von reinen Funktionen hängt nur von der Eingabe ab. Wenn sie ausgeführt werden, haben sie keinen Einfluss auf den Status des Programms.
Funktionale Programmierprinzipien werden von vielen Sprachen unterstützt. Unter ihnen sind JavaScript, Haskell, Clojure, Erlang. Die Verwendung funktionaler Programmiermechanismen setzt unter anderem die Kenntnis von Konzepten wie reinen Funktionen, Curry-Funktionen und Funktionen höherer Ordnung voraus.

Das Material, das wir heute übersetzen, handelt vom Curry. Wir werden darüber sprechen, wie Currying funktioniert und wie das Wissen über diesen Mechanismus für einen JS-Entwickler nützlich sein kann.
Was ist Curry?
Currying in der funktionalen Programmierung ist die Umwandlung einer Funktion mit vielen Argumenten in eine Reihe verschachtelter Funktionen mit einem Argument. Wenn eine Curry-Funktion mit einem übergebenen Argument aufgerufen wird, gibt sie eine neue Funktion zurück, die erwartet, dass das nächste Argument eintrifft. Neue Funktionen, die auf das nächste Argument warten, werden bei jedem Aufruf der Curry-Funktion zurückgegeben - bis die Funktion alle benötigten Argumente erhält. Die Argumente, die dank des Schließmechanismus früher eingegangen sind, warten auf den Moment, in dem die Funktion alles erhält, was sie zur Durchführung der Berechnungen benötigt. Nach Erhalt des letzten Arguments führt die Funktion die Berechnung durch und gibt das Ergebnis zurück.
Wenn wir vom
Curry sprechen, können wir sagen, dass dies der Prozess ist, eine Funktion mit mehreren Argumenten in eine Funktion mit weniger Arität umzuwandeln.
Arity ist die Anzahl der Argumente für eine Funktion. Hier ist zum Beispiel die Deklaration eines Funktionspaares:
function fn(a, b) { //... } function _fn(a, b, c) { //... }
Die
fn
Funktion
_fn
zwei Argumente (dies ist eine binäre oder 2-ary-Funktion), die
_fn
Funktion
_fn
drei Argumente (eine ternäre 3-ary-Funktion).
Lassen Sie uns über die Situation sprechen, in der während des Currying eine Funktion mit mehreren Argumenten in eine Reihe von Funktionen konvertiert wird, von denen jede ein Argument akzeptiert.
Betrachten Sie ein Beispiel. Wir haben folgende Funktion:
function multiply(a, b, c) { return a * b * c; }
Es dauert drei Argumente und gibt ihr Produkt zurück:
multiply(1,2,3);
Lassen Sie uns nun darüber nachdenken, wie Sie es in eine Reihe von Funktionen konvertieren können, von denen jede ein Argument benötigt. Lassen Sie uns eine Curry-Version dieser Funktion erstellen und untersuchen, wie Sie beim Aufrufen mehrerer Funktionen dasselbe Ergebnis erzielen:
function multiply(a) { return (b) => { return (c) => { return a * b * c } } } log(multiply(1)(2)(3))
Wie Sie sehen können, haben wir hier den Aufruf in eine einzelne Funktion mit drei Argumenten konvertiert -
multiply(1,2,3)
mit dem Aufruf in drei Funktionen -
multiply(1)(2)(3)
.
Es stellt sich heraus, dass aus einer Funktion mehrere Funktionen geworden sind. Bei Verwendung der neuen Konstruktion nimmt jede Funktion mit Ausnahme der letzten, die das Ergebnis von Berechnungen zurückgibt, ein Argument und eine andere Funktion zurück, die ebenfalls ein Argument akzeptieren und eine andere Funktion zurückgeben kann. Wenn Ihnen eine Konstruktion der Form
multiply(1)(2)(3)
nicht sehr klar erscheint, schreiben wir sie in diese Form, um dies besser zu verstehen:
const mul1 = multiply(1); const mul2 = mul1(2); const result = mul2(3); log(result);
Lassen Sie uns nun Zeile für Zeile wissen, was hier passiert.
Zuerst übergeben wir Argument
1
an die
multiply
:
const mul1 = multiply(1);
Wenn diese Funktion funktioniert, funktioniert dieses Design:
return (b) => { return (c) => { return a * b * c } }
Jetzt hat
mul1
einen Verweis auf eine Funktion, die ein Argument
mul1
.
b
Wir rufen die Funktion
mul1
und übergeben sie
2
:
const mul2 = mul1(2);
Als Ergebnis dieses Aufrufs wird der folgende Code ausgeführt:
return (c) => { return a * b * c }
Die
mul2
enthält einen Verweis auf eine Funktion, die sich beispielsweise aufgrund der folgenden Operation darin befinden könnte:
mul2 = (c) => { return a * b * c }
Wenn wir nun die Funktion
mul2
und
3
, führt die Funktion die erforderlichen Berechnungen mit den Argumenten
a
und
b
:
const result = mul2(3);
Das Ergebnis dieser Berechnungen ist
6
:
log(result)
Die
mul2
Funktion, die die höchste Verschachtelungsebene aufweist, hat Zugriff auf den Bereich, auf die Abschlüsse, die durch die
multiply
und
mul1
. Aus diesem Grund können in der Funktion
mul2
Berechnungen mit Variablen durchgeführt werden, die in Funktionen deklariert sind, deren Ausführung bereits abgeschlossen wurde, die bereits einige Werte zurückgegeben haben und vom Garbage Collector verarbeitet werden.
Oben haben wir ein abstraktes Beispiel untersucht, aber im Wesentlichen dieselbe Funktion, mit der das Volumen einer rechteckigen Box berechnet werden soll.
function volume(l,w,h) { return l * w * h; } const vol = volume(100,20,90)
So sieht die Curry-Version aus:
function volume(l) { return (w) => { return (h) => { return l * w * h } } } const vol = volume(100)(20)(90)
Das Currying basiert also auf der folgenden Idee: Auf der Grundlage einer bestimmten Funktion wird eine andere Funktion erstellt, die eine spezielle Funktion zurückgibt.
Currying und teilweise Nutzung von Funktionen
Vielleicht besteht nun das Gefühl, dass die Anzahl der verschachtelten Funktionen, wenn eine Funktion als Satz verschachtelter Funktionen dargestellt wird, von der Anzahl der Argumente für die Funktion abhängt. Und wenn es um Curry geht, dann ist es das auch.
Eine spezielle Version der Funktion zur Berechnung des Volumens, die wir bereits gesehen haben, kann wie folgt erstellt werden:
function volume(l) { return (w, h) => { return l * w * h } }
Hier werden Ideen angewendet, die den oben diskutierten sehr ähnlich sind. Sie können diese Funktion wie folgt verwenden:
const hV = volume(70); hV(203,142); hV(220,122); hV(120,123);
Und Sie können dies tun:
volume(70)(90,30)
In der Tat können Sie hier sehen, wie wir mit dem Befehl
volume(70)
eine spezielle Funktion zur Berechnung des Volumens von Körpern erstellt haben, deren Dimensionen (nämlich Länge,
l
) festgelegt sind. Die
volume
Funktion erwartet 3 Argumente und enthält 2 verschachtelte Funktionen, im Gegensatz zur vorherigen Version einer ähnlichen Funktion, deren Curry-Version 3 verschachtelte Funktionen enthielt.
Die Funktion, die nach dem Aufruf von
volume(70)
implementiert das Konzept einer Teilfunktionsanwendung. Currying und teilweise Anwendung von Funktionen sind einander sehr ähnlich, aber die Konzepte sind unterschiedlich.
Bei teilweiser Anwendung wird die Funktion mit weniger Argumenten (weniger Arität) in eine andere Funktion umgewandelt. Einige Argumente einer solchen Funktion sind fest (für sie sind Standardwerte festgelegt).
Zum Beispiel gibt es eine solche Funktion:
function acidityRatio(x, y, z) { return performOp(x,y,z) }
Es kann in Folgendes konvertiert werden:
function acidityRatio(x) { return (y,z) => { return performOp(x,y,z) } }
Die Implementierung der Funktion
performOp()
wird hier nicht angegeben, da sie die betrachteten Konzepte nicht beeinflusst.
Die Funktion, die durch Aufrufen der neuen Funktion
acidityRatio()
mit einem Argument erhalten werden kann, dessen Wert festgelegt werden muss, ist die ursprüngliche Funktion, von der eines der Argumente festgelegt ist, und diese Funktion selbst benötigt ein Argument weniger als das ursprüngliche.
Die Curry-Version der Funktion sieht folgendermaßen aus:
function acidityRatio(x) { return (y) = > { return (z) = > { return performOp(x,y,z) } } }
Wie Sie sehen können, entspricht die Anzahl der verschachtelten Funktionen beim Curry der Anzahl der Argumente der ursprünglichen Funktion. Jede dieser Funktionen erwartet ein eigenes Argument. Es ist klar, dass wenn die Funktion von Argumenten nicht oder nur ein Argument akzeptiert, es nicht Curry werden kann.
In einer Situation, in der eine Funktion zwei Argumente hat, kann gesagt werden, dass die Ergebnisse ihrer Currying- und Teilanwendung zusammenfallen. Zum Beispiel haben wir eine solche Funktion:
function div(x,y) { return x/y; }
Angenommen, wir müssen es neu schreiben, damit wir beim Korrigieren des ersten Arguments eine Funktion erhalten, die Berechnungen ausführt, wenn nur das zweite Argument an das Argument übergeben wird, dh wir müssen diese Funktion teilweise anwenden. Es wird so aussehen:
function div(x) { return (y) => { return x/y; } }
Das Ergebnis des Currying sieht genauso aus.
Zur praktischen Anwendung der Konzepte des Currying und der teilweisen Anwendung von Funktionen
Currying und teilweise Anwendung von Funktionen können in verschiedenen Situationen nützlich sein. Zum Beispiel bei der Entwicklung kleiner Module, die zur Wiederverwendung geeignet sind.
Die teilweise Verwendung von Funktionen erleichtert die Verwendung von Universalmodulen. Zum Beispiel haben wir einen Online-Shop, in dessen Code es eine Funktion gibt, mit der der zu zahlende Betrag unter Berücksichtigung des Rabatts berechnet wird.
function discount(price, discount) { return price * discount }
Es gibt eine bestimmte Kategorie von Kunden, nennen wir sie „geliebte Kunden“, denen wir 10% Rabatt gewähren. Wenn ein solcher Kunde beispielsweise etwas für 500 US-Dollar kauft, gewähren wir ihm einen Rabatt von 50 US-Dollar:
const price = discount(500,0.10); // $50 // $500 - $50 = $450
Es ist leicht zu bemerken, dass wir bei diesem Ansatz diese Funktion ständig mit zwei Argumenten aufrufen müssen:
const price = discount(1500,0.10); // $150 // $1,500 - $150 = $1,350 const price = discount(2000,0.10); // $200 // $2,000 - $200 = $1,800 const price = discount(50,0.10); // $5 // $50 - $5 = $45 const price = discount(5000,0.10); // $500 // $5,000 - $500 = $4,500 const price = discount(300,0.10); // $30 // $300 - $30 = $270
Die ursprüngliche Funktion kann auf ein Formular reduziert werden, mit dem Sie neue Funktionen mit einer festgelegten Rabattstufe erhalten können, wenn Sie anrufen, um den Kaufbetrag zu überweisen. Die Funktion
discount()
in unserem Beispiel hat zwei Argumente. So sieht es aus, in das wir es konvertieren:
function discount(discount) { return (price) => { return price * discount; } } const tenPercentDiscount = discount(0.1);
Die Funktion
tenPercentDiscount()
ist das Ergebnis einer teilweisen Anwendung der Funktion
discount()
. Wenn Sie
tenPercentDiscount()
dieser Funktion aufrufen, reicht es aus, den Preis zu übergeben, und ein Rabatt von 10%,
tenPercentDiscount()
das
discount
, wird bereits festgelegt:
tenPercentDiscount(500); // $50 // $500 - $50 = $450
Wenn es Käufer in unserem Geschäft gibt, die beschlossen haben, einen Rabatt von 20% zu gewähren, können Sie die entsprechende Funktion erhalten, um mit ihnen wie folgt zu arbeiten:
const twentyPercentDiscount = discount(0.2);
Jetzt kann die Funktion
twentyPercentDiscount()
aufgerufen werden, um die Kosten der Waren unter Berücksichtigung eines Rabattes von 20% zu berechnen:
twentyPercentDiscount(500); // 100 // $500 - $100 = $400 twentyPercentDiscount(5000); // 1000 // $5,000 - $1,000 = $4,000 twentyPercentDiscount(1000000); // 200000 // $1,000,000 - $200,000 = $600,000
Universalfunktion zur teilweisen Anwendung anderer Funktionen
Wir werden eine Funktion entwickeln, die jede Funktion akzeptiert und ihre Variante zurückgibt, bei der es sich um eine Funktion handelt, deren Argumente bereits festgelegt sind. Hier ist der Code, mit dem Sie dies tun können (wenn Sie eine ähnliche Funktion entwickeln möchten, ist es durchaus möglich, dass Sie dadurch etwas anderes erhalten):
function partial(fn, ...args) { return (..._arg) => { return fn(...args, ..._arg); } }
Die Funktion
partial()
akzeptiert die Funktion
fn
, die wir in die teilweise angewendete Funktion konvertieren möchten, und eine variable Anzahl von Parametern
(...args
). Die
rest
Anweisung wird verwendet, um alle Parameter nach
fn
in
args
.
Diese Funktion gibt eine andere Funktion zurück, die auch eine variable Anzahl von Parametern (
_arg
) akzeptiert. Diese Funktion ruft wiederum die ursprüngliche
fn
Funktion auf und
..._arg
ihr die Parameter
...args
und
..._arg
(unter Verwendung des
spread
Operators). Die Funktion führt die Berechnung durch und gibt das Ergebnis zurück.
Mit dieser Funktion erstellen wir eine Variante der Ihnen bereits bekannten Volumenfunktion, mit der das Volumen rechteckiger Parallelepipeds berechnet wird, deren eine Seite feststeht:
function volume(l,h,w) { return l * h * w } const hV = partial(volume,100); hV(200,900); // 18000000 hV(70,60); // 420000
Hier finden Sie ein Beispiel für eine universelle Funktion zum Curryen anderer Funktionen.
Zusammenfassung
In diesem Artikel haben wir über das Currying und die teilweise Anwendung von Funktionen gesprochen. Diese Methoden zum Transformieren von Funktionen werden in JavaScript aufgrund von Abschlüssen und aufgrund der Tatsache implementiert, dass Funktionen in JS Objekte der ersten Klasse sind (sie können als Argumente an andere Funktionen übergeben, von ihnen zurückgegeben und Variablen zugewiesen werden).
Liebe Leser! Verwenden Sie in Ihren Projekten Curry-Techniken und teilweise Anwendung von Funktionen?
