Werden Verschlüsse mit Nebenwirkungen als „funktionaler Stil“ betrachtet?

9

Viele moderne Programmiersprachen unterstützen ein Konzept des Schließens , dh eines Codes (eines Blocks oder einer Funktion)

  1. Kann als Wert behandelt und daher in einer Variablen gespeichert, an verschiedene Teile des Codes weitergegeben, in einem Teil eines Programms definiert und in einem völlig anderen Teil desselben Programms aufgerufen werden.
  2. Kann Variablen aus dem Kontext erfassen, in dem sie definiert sind, und auf sie zugreifen, wenn sie später aufgerufen werden (möglicherweise in einem völlig anderen Kontext).

Hier ist ein Beispiel für einen Abschluss in Scala:

def filterList(xs: List[Int], lowerBound: Int): List[Int] =
  xs.filter(x => x >= lowerBound)

Das Funktionsliteral x => x >= lowerBoundenthält die freie Variable lowerBound, die durch das Argument der gleichnamigen Funktion geschlossen (gebunden) wird filterList. Der Abschluss wird an die Bibliotheksmethode übergeben filter, die ihn als normale Funktion wiederholt aufrufen kann.

Ich habe auf dieser Website viele Fragen und Antworten gelesen, und soweit ich weiß, wird der Begriff Abschluss häufig automatisch mit funktionaler Programmierung und funktionalem Programmierstil assoziiert .

Die Definition der Funktionsprogrammierung auf Wikipedia lautet:

In der Informatik ist die funktionale Programmierung ein Programmierparadigma, das die Berechnung als Bewertung mathematischer Funktionen behandelt und Zustands- und veränderbare Daten vermeidet. Es betont die Anwendung von Funktionen im Gegensatz zum imperativen Programmierstil, der Zustandsänderungen betont.

und weiter

[...] im Funktionscode hängt der Ausgabewert einer Funktion nur von den Argumenten ab, die in die Funktion [...] eingegeben werden. Die Beseitigung von Nebenwirkungen kann das Verständnis und die Vorhersage des Verhaltens eines Programms erheblich erleichtern. Dies ist eine der Hauptmotive für die Entwicklung der funktionalen Programmierung.

Andererseits ermöglichen viele von Programmiersprachen bereitgestellte Abschlusskonstrukte, dass ein Abschluss nicht lokale Variablen erfasst und beim Aufruf des Abschlusses ändert, wodurch ein Nebeneffekt auf die Umgebung erzeugt wird, in der sie definiert wurden.

In diesem Fall implementieren Verschlüsse die erste Idee der funktionalen Programmierung (Funktionen sind erstklassige Entitäten, die wie andere Werte verschoben werden können), vernachlässigen jedoch die zweite Idee (Vermeidung von Nebenwirkungen).

Wird diese Verwendung von Verschlüssen mit Nebenwirkungen als funktionaler Stil betrachtet oder werden Verschlüsse als allgemeineres Konstrukt betrachtet, das sowohl für einen funktionalen als auch für einen nicht funktionalen Programmierstil verwendet werden kann? Gibt es Literatur zu diesem Thema?

WICHTIGE NOTIZ

Ich bezweifle nicht die Nützlichkeit von Nebenwirkungen oder Verschlüssen mit Nebenwirkungen. Ich bin auch nicht an einer Diskussion über die Vor- und Nachteile von Verschlüssen mit oder ohne Nebenwirkungen interessiert.

Ich bin nur daran interessiert zu wissen, ob die Verwendung solcher Verschlüsse vom Befürworter der funktionalen Programmierung immer noch als funktionaler Stil angesehen wird oder ob im Gegenteil von ihrer Verwendung bei Verwendung eines funktionalen Stils abgeraten wird.

Giorgio
quelle
3
In einem rein funktionalen Stil / einer rein funktionalen Sprache sind Nebenwirkungen unmöglich ... also denke ich, es wäre eine Frage von: Auf welchem ​​Reinheitsgrad betrachtet man Code als funktional?
Steven Evers
@ SnOrfus: Bei 100%?
Giorgio
1
Nebenwirkungen sind in einer rein funktionalen Sprache nicht möglich, daher sollte dies Ihre Frage beantworten.
Steven Evers
@ SnOrfus: In vielen Sprachen werden Verschlüsse als funktionales Programmierkonstrukt angesehen, daher war ich etwas verwirrt. Es scheint mir, dass (1) ihre Verwendung allgemeiner ist als nur FP, und (2) die Verwendung von Verschlüssen nicht automatisch garantiert, dass man einen funktionalen Stil verwendet.
Giorgio
Kann der Downvoter eine Notiz hinterlassen, wie diese Frage verbessert werden kann?
Giorgio

Antworten:

7

Nein; Bei der Definition des funktionalen Paradigmas geht es um mangelnden Zustand und implizit um fehlende Nebenwirkungen. Es geht nicht um Funktionen höherer Ordnung, Schließungen, sprachunterstützte Listenmanipulation oder andere Sprachfunktionen ...

Der Name der funktionalen Programmierung leitet sich vom mathematischen Begriff der Funktionen ab - wiederholte Aufrufe derselben Eingabe ergeben immer dieselbe Ausgabe - nullipotente Funktion. Dies kann nur erreicht werden, wenn die Daten unveränderlich sind . Um die Entwicklung zu vereinfachen, wurden die Funktionen veränderlich (Funktionen ändern sich, Daten sind immer noch unveränderlich) und damit der Begriff von Funktionen höherer Ordnung (Funktionen in der Mathematik, beispielsweise als Ableitungen) - eine Funktion, die eine andere Funktion als Eingabe verwendet. Für die Möglichkeit, Funktionen herumzutragen und als Argumente weiterzugeben, wurden erstklassige Funktionen übernommen. Im Anschluss daran wurden zur weiteren Steigerung der Produktivität Schließungen vorgenommen .

Dies ist natürlich eine sehr vereinfachte Ansicht.

m3th0dman
quelle
3
Funktionen höherer Ordnung haben absolut nichts mit Veränderlichkeit zu tun, ebenso wenig wie erstklassige Funktionen. Ich kann Funktionen in Haskell mit Leichtigkeit weitergeben und andere Funktionen daraus konstruieren, ohne veränderlichen Zustand oder Nebenwirkungen zu haben.
tdammers
@tdammers habe ich wohl nicht sehr deutlich ausgedrückt; Als ich sagte, dass Funktionen veränderbar wurden, bezog ich mich nicht auf die Veränderbarkeit von Daten, sondern auf die Tatsache, dass das Verhalten einer Funktion geändert werden kann (durch eine Funktion höherer Ordnung).
m3th0dman
Eine Funktion höherer Ordnung muss eine vorhandene Funktion nicht ändern. Die meisten Lehrbuchbeispiele kombinieren vorhandene Funktionen zu neuen Funktionen, ohne sie zu ändern. Nehmen Sie mapzum Beispiel eine Funktion, wendet sie auf eine Liste an und gibt eine Liste der Ergebnisse zurück. mapändert keines seiner Argumente, es ändert nicht das Verhalten der Funktion, die es als Argument verwendet, aber es ist definitiv eine Funktion höherer Ordnung - wenn Sie sie teilweise anwenden, nur mit dem Funktionsparameter, haben Sie eine konstruiert Neue Funktion, die eine Liste bearbeitet, aber noch keine Mutation aufgetreten ist.
tdammers
@tdammers: Genau: Mutabilität wird nur in imperativen oder Multi-Paradigmen-Sprachen verwendet. Obwohl diese das Konzept einer Funktion (oder Methode) höherer Ordnung und eines Verschlusses haben können, geben sie die Unveränderlichkeit auf. Dies kann nützlich sein (ich sage nicht, dass man es nicht tun sollte), aber meine Frage ist, ob Sie diese Funktion noch aufrufen können. Dies würde bedeuten, dass ein Verschluss im Allgemeinen kein rein funktionales Konzept ist.
Giorgio
@Giorgio: Die meisten der klassischen FP Sprachen tun Veränderlichkeit haben; Haskell ist der einzige, an den ich auf Anhieb denken kann, der überhaupt keine Veränderlichkeit zulässt. Die Vermeidung eines veränderlichen Zustands ist jedoch ein wichtiger Wert in FP.
tdammers
9

Nein. "Functional-Style" impliziert eine nebenwirkungsfreie Programmierung.

Um zu sehen, warum, werfen Sie einen Blick auf Eric Lipperts Blogeintrag über die ForEach<T>Erweiterungsmethode und warum Microsoft eine solche Sequenzmethode nicht in Linq aufgenommen hat :

Ich bin aus zwei Gründen philosophisch gegen eine solche Methode.

Der erste Grund ist, dass dies gegen die funktionalen Programmierprinzipien verstößt, auf denen alle anderen Sequenzoperatoren basieren. Der einzige Zweck eines Aufrufs dieser Methode besteht eindeutig darin, Nebenwirkungen zu verursachen. Der Zweck eines Ausdrucks besteht darin, einen Wert zu berechnen und keine Nebenwirkung zu verursachen. Der Zweck einer Aussage ist es, eine Nebenwirkung zu verursachen. Die Aufrufstelle dieser Sache würde sehr nach einem Ausdruck aussehen (obwohl der Ausdruck zugegebenermaßen nur in einem "Anweisungsausdruck" -Kontext verwendet werden kann, da die Methode nichtig ist.) Das passt nicht gut zu mir Machen Sie den einzigen Sequenzoperator, der nur für seine Nebenwirkungen nützlich ist.

Der zweite Grund ist, dass dadurch der Sprache keine neue Repräsentationskraft hinzugefügt wird. Auf diese Weise können Sie diesen vollkommen klaren Code neu schreiben:

foreach(Foo foo in foos){ statement involving foo; }

in diesen Code:

foos.ForEach((Foo foo)=>{ statement involving foo; });

Dabei werden fast genau dieselben Zeichen in leicht unterschiedlicher Reihenfolge verwendet. Die zweite Version ist jedoch schwerer zu verstehen, schwerer zu debuggen und führt die Abschlusssemantik ein, wodurch sich die Lebensdauer von Objekten möglicherweise auf subtile Weise ändert.

Robert Harvey
quelle
1
Obwohl ich ihm zustimme ... schwächt sich sein Argument ein wenig ab, wenn man darüber nachdenkt ParallelQuery<T>.ForAll(...). Die Implementierung eines solchen IEnumerable<T>.ForEach(...)ist äußerst nützlich für das Debuggen von ForAllAnweisungen (ersetzen Sie das ForAlldurch ForEachund entfernen Sie das, AsParallel()und Sie können es viel einfacher durchlaufen / debuggen)
Steven Evers
Offensichtlich hat Joe Duffy andere Ideen. : D
Robert Harvey
Scala erzwingt auch keine Reinheit: Sie können einen nicht reinen Verschluss an eine Funktion höherer Ordnung übergeben. Mein Eindruck ist, dass die Idee eines Abschlusses nicht spezifisch für die funktionale Programmierung ist, sondern eine allgemeinere Idee.
Giorgio
5
@Giorgio: Verschlüsse müssen nicht rein sein, um weiterhin als Verschlüsse zu gelten. Sie müssen jedoch rein sein, um als "funktionaler Stil" betrachtet zu werden.
Robert Harvey
3
@Giorgio: Es ist wirklich nützlich, veränderbare Zustände und Nebenwirkungen zu schließen und an eine andere Funktion weiterzugeben. Es widerspricht einfach den Zielen der funktionalen Programmierung. Ich denke, dass viel Verwirrung durch die elegante Sprachunterstützung für Lambdas entsteht, die in funktionalen Sprachen üblich sind.
GlenPeterson
0

Funktionale Programmierung bringt erstklassige Funktionen sicher auf die nächste konzeptionelle Ebene, aber das Deklarieren anonymer Funktionen oder das Übergeben von Funktionen an andere Funktionen ist nicht unbedingt eine funktionale Programmiersache. In C war alles eine ganze Zahl. Eine Zahl, ein Zeiger auf Daten, ein Zeiger auf eine Funktion ... alles nur ints. Sie könnten Funktionszeiger an andere Funktionen übergeben, Listen mit Funktionszeigern erstellen ... Wenn Sie in Assemblersprache arbeiten, sind Funktionen eigentlich nur Adressen im Speicher, in denen Blöcke von Maschinenanweisungen gespeichert sind. Das Vergeben eines Namens für eine Funktion ist ein zusätzlicher Aufwand für die Personen, die einen Compiler zum Schreiben von Code benötigen. Funktionen waren also in diesem Sinne "erstklassig" in einer völlig nicht funktionierenden Sprache.

Wenn Sie nur mathematische Formeln in einer REPL berechnen, können Sie mit Ihrer Sprache funktional rein sein. Die meisten Business-Programme haben jedoch Nebenwirkungen. Ein Nebeneffekt ist es, Geld zu verlieren, während Sie auf den Abschluss eines lang laufenden Programms warten. Das Ausführen externer Maßnahmen: Das Schreiben in eine Datei, das Aktualisieren einer Datenbank, das Protokollieren von Ereignissen in der richtigen Reihenfolge usw. erfordert eine Statusänderung. Wir könnten darüber diskutieren, ob sich der Status wirklich ändert, wenn Sie diese Aktionen in unveränderlichen Wrappern kapseln, die Nebenwirkungen abschrecken, damit sich Ihr Code nicht darum kümmern muss. Aber es ist wie zu diskutieren, ob ein Baum ein Geräusch macht, wenn er in den Wald fällt, ohne dass jemand da ist, der es hört. Tatsache ist, dass der Baum aufrecht begann und auf dem Boden landete. Der Status wird geändert, wenn Dinge erledigt sind, auch wenn nur gemeldet wird, dass die Dinge erledigt wurden.

Wir haben also eine Skala funktionaler Reinheit, nicht Schwarz und Weiß, sondern Grautöne. Und in dieser Größenordnung gilt: Je weniger Nebenwirkungen, desto geringer die Veränderlichkeit, desto besser (funktioneller).

Wenn Sie in Ihrem ansonsten funktionalen Code unbedingt Nebenwirkungen oder einen veränderlichen Zustand benötigen, bemühen Sie sich, diese oder den Rest Ihres Programms so gut wie möglich zu kapseln. Die Verwendung eines Verschlusses (oder irgendetwas anderem), um Nebenwirkungen oder veränderlichen Zustand in ansonsten reine Funktionen zu injizieren, ist das Gegenteil der funktionalen Programmierung. Die einzige Ausnahme könnte sein, wenn der Abschluss der effektivste Weg wäre, die Nebenwirkungen des Codes, an den er übergeben wird, zu kapseln. Es ist immer noch keine "funktionale Programmierung", aber es könnte der Schrank sein, den Sie in bestimmten Situationen bekommen können.

GlenPeterson
quelle