Funktionale JavaScript-Programmierung mit Ramda

Ursprünglicher Autor: Andrew D'Amelio und Yuri Takhteyev
  • Übersetzung
Wir bei rangle.io beschäftigen uns seit langem mit funktionaler Programmierung und haben bereits Underscore und Lodash ausprobiert . Aber kürzlich sind wir auf die Ramda- Bibliothek gestoßen , die auf den ersten Blick wie Unterstrich aussieht, sich aber in einem kleinen, aber wichtigen Bereich unterscheidet. Ramda bietet ungefähr die gleichen Methoden wie Underscore an, organisiert jedoch die Arbeit mit ihnen so, dass die funktionale Zusammensetzung einfacher wird.

Der Unterschied zwischen Ramda und Underscore liegt in zwei Schlüsselbereichen - Currying und Komposition.

Currying


Currying ist die Umwandlung einer Funktion, die mehrere Parameter erwartet, in einen, der bei Übergabe weniger Parameter eine neue Funktion zurückgibt, die auf die verbleibenden Parameter wartet.

R.multiply(2, 10); // возвращает 20


Wir haben beide Parameter an die Funktion übergeben.

var multiplyByTwo = R.multiply(2);
multiplyByTwo(10); // возвращает 20


Cool. Wir haben eine neue multiplyByTwo-Funktion erstellt, die im Wesentlichen 2 ist und in multiply () integriert ist. Jetzt können Sie einen beliebigen Wert an unser multiplyByTwo übergeben. Und vielleicht liegt das daran, dass in Ramda alle Funktionen das Curry unterstützen.

Der Prozess geht von rechts nach links: Wenn Sie einige Argumente überspringen, geht Ramda davon aus, dass Sie die rechts übersprungen haben. Daher erwarten Funktionen, die ein Array und eine Funktion verwenden, normalerweise die Funktion als erstes Argument und das Array als zweites. Aber in Underscore ist das Gegenteil der Fall:

_.map([1,2,3], _.add(1)) // 2,3,4


Gegen:

R.map(R.add(1), [1,2,3]); // 2,3,4


Durch die Kombination des Ansatzes „Erstoperation, dann Daten“ mit Currying von rechts nach links können wir festlegen, was zu tun ist, und zu der Funktion zurückkehren, die dies tun wird. Dann können wir die notwendigen Daten an diese Funktion übergeben. Curry ist einfach und praktisch.

var addOneToAll = R.map(R.add(1));
addOneToAll([1,2,3]); // возвращает 2,3,4


Hier ist ein Beispiel komplizierter. Angenommen, wir stellen eine Anfrage an den Server, erhalten ein Array und extrahieren den Kostenwert aus jedem Element. Mit Unterstrich könnte man dies tun:

return getItems()
  .then(function(items){
    return _.pluck(items, 'cost');
});


Mit Ramda können Sie unnötige Operationen entfernen:

return getItems()
    .then(R.pluck('cost'));


Wenn wir R.pluck ('cost') aufrufen, wird eine Funktion zurückgegeben, die die Kosten aus jedem Element des Arrays extrahiert. Und genau das müssen wir an .then () übergeben. Für ein vollkommenes Glück ist es jedoch notwendig, Curry mit der Komposition zu kombinieren.

Zusammensetzung


Eine funktionale Zusammensetzung ist eine Operation, die die Funktionen f und g übernimmt und eine Funktion h zurückgibt, so dass h (x) = f (g (x)). Ramda hat dafür eine compose () -Funktion. Durch die Kombination dieser beiden Konzepte können wir die komplexe Arbeit von Funktionen aus kleineren Komponenten zusammensetzen.

var getCostWithTax = R.compose(
    R.multiply(1 + TAX_RATE), // подсчитаем налог
    R.prop('cost') // вытащим свойство 'cost' 
);


Es stellt sich heraus, dass eine Funktion den Wert aus dem Objekt zieht und das Ergebnis mit 1,13 multipliziert.

Die Standardfunktion „Compose“ führt Operationen von rechts nach links aus. Wenn Ihnen dies nicht intuitiv erscheint, können Sie R.pipe (), das funktioniert, R.compose () nur von links nach rechts verwenden:

var getCostWithTax = R.pipe(
    R.prop('cost'), // вытащим свойство 'cost' 
    R.multiply(1 + TAX_RATE) // подсчитаем налог
);


Die Funktionen R.compose und R.pipe können bis zu 10 Argumente annehmen.

Underscore unterstützt natürlich auch das Currying und die Komposition, wird dort jedoch selten verwendet, da das Currying in Underscore unpraktisch ist. Ramda macht es einfach, diese beiden Techniken zu kombinieren.

Zuerst haben wir uns in Ramda verliebt. Ihr Stil generiert erweiterbaren, deklarativen Code, der leicht zu testen ist. Die Komposition wird auf natürliche Weise ausgeführt und führt zu leicht verständlichem Code. Aber dann ...

Wir haben festgestellt, dass die Dinge verwirrender werden, wenn asynchrone Funktionen verwendet werden, die Versprechen zurückgeben:

var getCostWithTaxAsync = function() {
    var getCostWithTax = R.pipe(
        R.prop('cost'), // вытащим свойство 'cost' 
        R.multiply(1 + TAX_RATE) // умножим его на 1.13
    );
    return getItem()
        .then(getCostWithTax);
}


Das ist natürlich besser als ohne Ramda, aber ich würde gerne etwas bekommen wie:

var getCostWithTaxAsync = R.pipe(
    getItem, // получим элемент
    R.prop('cost'), // вытащим свойство 'cost' 
    R.multiply(1 + TAX_RATE) // умножим на 1.13
);


Dies funktioniert jedoch nicht, da getItem () ein Versprechen zurückgibt und die von R.prop () zurückgegebene Funktion einen Wert erwartet.

Versprochene Zusammensetzung


Wir haben die Ramda-Entwickler kontaktiert und eine Version der Komposition vorgeschlagen, die Versprechen automatisch auspackt, und asynchrone Funktionen können Funktionen zugeordnet werden, die einen Wert erwarten. Nach langen Diskussionen haben wir uns darauf geeinigt, diesen Ansatz in Form neuer Funktionen umzusetzen: R.pCompose () und R.pPipe () - wobei „p“ „Versprechen“ bedeutet.

Und mit R.pPipe können wir tun, was wir brauchen:

var getCostWithTaxAsync = R.pPipe(
    getItem, // получим обещание
    R.prop('cost'), // вытащим свойство 'cost'
    R.multiply(1 + TAX_RATE) // умножим на 1.13
); // возвращает обещание и cost с налогом

Jetzt auch beliebt: