Looking for functional-programming Keywords? Try Ask4Keywords

functional-programmingErste Schritte mit der Funktionsprogrammierung


Bemerkungen

Funktionale Programmierung ist ein Programmierparadigma, das Berechnungen (und damit Programme) als Bewertung mathematischer Funktionen modelliert. Es hat seine Wurzeln im Lambda-Kalkül, der von Alonzo Church in seiner Erforschung der Berechenbarkeit entwickelt wurde.

Funktionale Programmierung hat einige interessante Konzepte:

  • Funktionen höherer Ordnung
  • Reinheit
  • Rekursion
  • Faulheit
  • Referentielle Transparenz
  • Currying
  • Functors
  • Monaden
  • Memoization & Rückrufoptimierung
  • Funktionseinheitstest

Beispiele für funktionale Programmiersprachen sind Lisp , Haskell , Scala und Clojure , aber auch andere Sprachen wie Python , R und Javascript ermöglichen das Schreiben von Teilen von Programmen in einem funktionalen Stil. Selbst in Java hat die funktionale Programmierung mit Lambda Expressions und der Stream API, die in Java 8 eingeführt wurden, ihren Platz gefunden.

Currying

Currying ist das Umwandeln einer Funktion, die mehrere Argumente in eine Folge von Funktionen überführt, von denen jede nur einen einzigen Parameter hat. Das Currying steht im Zusammenhang mit der teilweisen Anwendung, ist jedoch nicht dasselbe.

Betrachten wir die folgende Funktion in JavaScript:

var add = (x, y) => x + y
 

Wir können die Definition von Currying verwenden, um die Add-Funktion neu zu schreiben:

var add = x => y => x + y
 

Diese neue Version verwendet einen einzelnen Parameter ( x ) und gibt eine Funktion zurück, die einen einzelnen Parameter ( y übernimmt, der schließlich das Ergebnis der Addition von x und y zurückgibt.

var add5 = add(5)
var fifteen = add5(10) // fifteen = 15
 

Ein anderes Beispiel ist, wenn wir die folgenden Funktionen haben, die Strings in Klammern setzen:

var generalBracket = (prefix, str, suffix) => prefix + str + suffix
 

Jedes Mal, wenn wir generalBracket verwenden, generalBracket wir die Klammern übergeben:

var bracketedJim = generalBracket("{", "Jim", "}") // "{Jim}"
var doubleBracketedJim = generalBracket("{{", "Jim", "}}") // "{{Jim}}"
 

Wenn wir die Zeichenfolgen übergeben, die keine Klammern sind, liefert unsere Funktion immer noch ein falsches Ergebnis. Lassen Sie uns das beheben:

var generalBracket = (prefix, suffix) => str => prefix + str + suffix
var bracket = generalBracket("{", "}")
var doubleBracket = generalBracket("{{", "}}")
 

Beachten Sie, dass sowohl bracket als auch doubleBracket jetzt Funktionen sind, die auf ihren endgültigen Parameter warten:

var bracketedJim = bracket("Jim") // "{Jim}"
var doubleBracketedJim = doubleBracket("Jim") // "{{Jim}}"
 

Funktionen höherer Ordnung

Funktionen höherer Ordnung übernehmen andere Funktionen als Argumente und / oder geben sie als Ergebnisse zurück. Sie bilden die Bausteine ​​der funktionalen Programmierung. Die meisten funktionalen Sprachen haben zum Beispiel eine Art Filterfunktion. Dies ist eine Funktion höherer Ordnung, die eine Liste und ein Prädikat (Funktion, die wahr oder falsch zurückgibt) als Argumente verwendet.

Funktionen, die keine dieser Funktionen erfüllen, werden häufig als Funktionen first-order functions .

function validate(number,predicate) {
    if (predicate) {    // Is Predicate defined
        return predicate(number);
    }
    return false;
}
 

"Prädikat" ist hier eine Funktion, die auf eine Bedingung prüft, die ihre Argumente beinhaltet, und true oder false zurückgibt.

Ein Beispiel für den oben genannten Aufruf ist:

validate(someNumber, function(arg) {
    return arg % 10 == 0;
    }
);
 

Eine häufige Anforderung ist das Hinzufügen von Zahlen innerhalb eines Bereichs. Durch die Verwendung von Funktionen höherer Ordnung können wir diese Basisfähigkeit erweitern, indem Sie auf jede Zahl eine Transformationsfunktion anwenden, bevor Sie sie in die Summe aufnehmen.

Sie möchten alle Ganzzahlen innerhalb eines bestimmten Bereichs hinzufügen (mit Scala)

def sumOfInts(a: Int, b: Int): Int = {
  if(a > b) 0
  else a + sumOfInts(a+1, b)
}
 

Sie möchten Quadrate aller ganzen Zahlen innerhalb eines bestimmten Bereichs hinzufügen

def square(a: Int): Int = a * a

def sumOfSquares(a: Int, b: Int): Int = {
  if(a > b) 0
  else square(a) + sumOfSquares(a + 1, b)
}
 

Beachten Sie, dass diese Dinge eine Sache gemeinsam haben, dass Sie auf jedes Argument eine Funktion anwenden und diese dann hinzufügen möchten.

Erstellen Sie eine Funktion höherer Ordnung, um beides zu tun:

def sumHOF(f: Int => Int, a: Int, b: Int): Int = {
  if(a > b) 0
  else f(a) + sumHOF(f, a + 1, b)
}
 

Man kann es so nennen:

def identity(a: Int): Int = a

def square(a: Int): Int = a * a
 

Beachten Sie, dass sumOfInts und sumOfSquare folgendermaßen definiert werden können:

def sumOfInts(a: Int, b: Int): Int = sumHOF(identity, a, b)

def sumOfSquares(a: Int, b: Int): Int = sumHOF(square, a, b)
 

Wie Sie an diesem einfachen Beispiel sehen können, bieten Funktionen höherer Ordnung allgemeinere Lösungen und reduzieren die Duplizierung von Code.

Ich habe Scala By Example - von Martin Odersky als Referenz verwendet.

Unveränderlichkeit

In traditionellen objektorientierten Sprachen ist x = x + 1 ein einfacher und rechtlicher Ausdruck. In der funktionalen Programmierung ist dies jedoch illegal .

In der funktionalen Programmierung gibt es keine Variablen. Gespeicherte Werte werden nur aufgrund der Historie als Variablen bezeichnet. In der Tat sind sie Konstanten . Sobald x einen Wert annimmt, ist es dieser Wert für das Leben.

Wenn also eine Variable eine Konstante ist , wie können wir dann ihren Wert ändern?

Die Funktionale Programmierung behandelt Änderungen an Werten in einem Datensatz, indem eine Kopie des Datensatzes mit den geänderten Werten erstellt wird.

Zum Beispiel anstatt zu tun:

var numbers = [1, 2, 3];
numbers[0] += 1; // numbers = [2, 2, 3];
 

Sie machen:

var numbers = [1, 2, 3];
var newNumbers = numbers.map(function(number) {
    if (numbers.indexOf(number) == 0)
        return number + 1
    return number
});
console.log(newNumbers) // prints [2, 2, 3]
 

Und bei der funktionalen Programmierung gibt es keine Schleifen. Wir verwenden Rekursionsfunktionen oder Funktionen höherer Ordnung wie map , filter und reduce , um Schleifen zu vermeiden.

Erstellen wir eine einfache Schleife in JavaScript:

var acc = 0;
for (var i = 1; i <= 10; ++i)
    acc += i;
console.log(acc); // prints 55
 

Wir können es noch besser machen, indem wir die Lebensdauer von acc von global auf lokal ändern:

function sumRange(start, end, acc) {
    if (start > end)
        return acc;
    return sumRange(start + 1, end, acc + start)
}
console.log(sumRange(1, 10, 0)); // 55
 

Keine Variablen oder Schleifen bedeuten einfacheren, sichereren und besser lesbaren Code (insbesondere beim Debuggen oder Testen - Sie müssen sich nach einer Reihe von Anweisungen keine Gedanken über den Wert von x , er wird sich niemals ändern).

Reine Funktionen

Reine Funktionen sind in sich abgeschlossen und haben keine Nebenwirkungen. Bei gleichen Eingaben liefert eine reine Funktion immer denselben Ausgabewert.

Die folgende Funktion ist rein:

function pure(data) {
    return data.total + 3;
}
 

Diese Funktion ist jedoch nicht rein, da sie eine externe Variable ändert:

function impure(data) {
    data.total += 3;
    return data.total;
}
 

Beispiel:

data = {
    total: 6
};

pure(data);   // outputs: 9
impure(data); // outputs: 9 (but now data.total has changed)
impure(data); // outputs: 12