Obwohl dies manchmal so ausgedrückt wird, verhindert die funktionale Programmierung¹ keine Zustandsberechnungen. Was es tut, ist, den Programmierer zu zwingen, den Zustand explizit zu machen.
Nehmen wir zum Beispiel die Grundstruktur eines Programms mit einer imperativen Warteschlange (in einigen Pseudosprachen):
q := Queue.new();
while (true) {
if (Queue.is_empty(q)) {
Queue.add(q, producer());
} else {
consumer(Queue.take(q));
}
}
Die entsprechende Struktur mit einer funktionalen Warteschlangendatenstruktur (immer noch in einer imperativen Sprache, um jeweils einen Unterschied anzugehen) würde folgendermaßen aussehen:
q := Queue.empty;
while (true) {
if (q = Queue.empty) {
q := Queue.add(q, producer());
} else {
(tail, element) := Queue.take(q);
consumer(element);
q := tail;
}
}
Da die Warteschlange jetzt unveränderlich ist, ändert sich das Objekt selbst nicht. In diesem Pseudocode ist q
selbst eine Variable; die Aufgaben q := Queue.add(…)
undq := tail
machen es auf ein anderes Objekt zeigen. Die Schnittstelle der Queue-Funktionen hat sich geändert: Jedes muss das neue Queue-Objekt zurückgeben, das sich aus der Operation ergibt.
In einer rein funktionalen Sprache, dh in einer Sprache ohne Nebeneffekte, müssen Sie all state explizit machen. Da Produzent und Konsument vermutlich etwas tun, muss auch hier ihr Zustand in der Aufruferschnittstelle stehen.
main_loop(q, other_state) {
if (q = Queue.empty) {
let (new_state, element) = producer(other_state);
main_loop(Queue.add(q, element), new_state);
} else {
let (tail, element) = Queue.take(q);
let new_state = consumer(other_state, element);
main_loop(tail, new_state);
}
}
main_loop(Queue.empty, initial_state)
Beachten Sie, wie jetzt jeder Zustand explizit verwaltet wird. Die Warteschlangenmanipulationsfunktionen nehmen eine Warteschlange als Eingabe und erzeugen eine neue Warteschlange als Ausgabe. Der Produzent und der Konsument geben auch ihren Staat durch.
Die gleichzeitige Programmierung passt nicht so gut in die funktionale Programmierung, passt aber sehr gut in die Umgebung funktionale Programmierung. Die Idee ist, eine Reihe separater Rechenknoten auszuführen und sie Nachrichten austauschen zu lassen. Jeder Knoten führt ein Funktionsprogramm aus, und sein Status ändert sich beim Senden und Empfangen von Nachrichten.
Wenn Sie das Beispiel fortsetzen, wird eine einzelne Warteschlange von einem bestimmten Knoten verwaltet. Verbraucher senden diesem Knoten eine Nachricht, um ein Element zu erhalten. Produzenten senden diesem Knoten eine Nachricht, um ein Element hinzuzufügen.
main_loop(q) =
consumer->consume(q->take()) || q->add(producer->produce());
main_loop(q)
Die einzige „industrialisierte“ Sprache, bei der die Parallelität stimmt³, ist Erlang . Das Erlernen von Erlang ist definitiv der Weg zur Aufklärung über die gleichzeitige Programmierung.
Jetzt schalten alle auf nebenwirkungsfreie Sprachen um!
¹ Dieser Begriff hat mehrere Bedeutungen. Ich denke, Sie meinen damit Programmieren ohne Nebenwirkungen, und das ist die Bedeutung, die ich auch benutze.
² Die Programmierung mit implizitem Status ist eine zwingende Programmierung . Objektorientierung ist ein völlig orthogonales Anliegen.
³ Entzündlich, ich weiß, aber ich meine es ernst. Threads mit Shared Memory sind die Assemblersprache für die gleichzeitige Programmierung. Die Weitergabe von Nachrichten ist viel einfacher zu verstehen, und das Fehlen von Nebenwirkungen wird deutlich, sobald Sie die Parallelität einführen.
⁴ Und das kommt von jemandem, der kein Fan von Erlang ist, aber aus anderen Gründen.