Beziehungen zwischen verschiedenen funktionalen Programmiervorstellungen

8

Ich verstehe verschiedene Begriffe der funktionalen Programmierung selbst: Nebenwirkungen, Unveränderlichkeit, reine Funktionen, referentielle Transparenz. Aber ich kann sie nicht in meinem Kopf verbinden. Zum Beispiel habe ich folgende Fragen:

  1. Was ist die Beziehung zwischen ref. Transparenz und Unveränderlichkeit. Bedeutet einer den anderen?

  2. Manchmal werden Nebenwirkungen und Unveränderlichkeit synonym verwendet. Ist es richtig?

damluar 24.08.2012, 19:10
quelle

3 Antworten

8

Diese Frage erfordert einige besonders pingelige Antworten, da es darum geht, ein gemeinsames Vokabular zu definieren.

Erstens ist eine Funktion eine Art mathematische Beziehung zwischen einer "Domäne" von Eingaben und einer "Reichweite" (oder Codomain) von Ausgaben. Jede Eingabe erzeugt eine eindeutige Ausgabe. Zum Beispiel akzeptiert die Ganzzahl-Additionsfunktion + die Eingabe in der Domäne Int x Int und erzeugt Ausgaben im Bereich Int .

%Vor%

Wenn Werte für x und y angegeben werden, wird + immer das gleiche Ergebnis liefern. Dies ist eine Funktion. Wenn der Compiler besonders clever wäre, könnte er Code einfügen, um die Ergebnisse dieser Funktion für jedes Paar von Eingängen zwischenzuspeichern, und eine Cache-Suche als Optimierung durchführen. Das ist hier eindeutig sicher.

Das Problem ist, dass in der Software der Begriff "Funktion" etwas missbraucht wurde: Obwohl Funktionen Argumente und Rückgabewerte akzeptieren, wie in ihrer Signatur deklariert, können sie auch in einen externen Kontext lesen und schreiben. Zum Beispiel:

%Vor%

Wir können uns das nicht mehr als mathematische Funktion vorstellen, denn bei einem gegebenen Wert von x kann + zu unterschiedlichen Ergebnissen führen (abhängig von einem zufälligen Wert, der nirgendwo in + erscheint) Unterschrift). Das Ergebnis von + kann nicht wie oben beschrieben sicher zwischengespeichert werden. Nun haben wir ein Vokabularproblem, das wir lösen, indem wir sagen, dass Ex0.+ rein ist und Ex1.+ nicht.

Okay, da wir jetzt ein gewisses Maß an Unreinheit akzeptiert haben, müssen wir definieren, um welche Art von Unreinheit es sich handelt! In diesem Fall haben wir gesagt, dass wir die Ergebnisse von Ex0.+ im Zusammenhang mit den Eingaben x und y zwischenspeichern können und dass wir nicht% c% speichern können. Die Ergebnisse sind mit der Eingabe Ex1.+ verknüpft. Der Begriff, den wir verwenden, um die Cachefähigkeit (oder genauer: die Substituierbarkeit eines Funktionsaufrufs mit seiner Ausgabe) zu beschreiben, ist referentielle Transparenz .

Alle reinen Funktionen sind referenziell transparent, aber einige referentiell transparente Funktionen sind nicht rein. Zum Beispiel:

%Vor%

Hier lesen wir nicht aus einem externen Kontext, und der von x für alle Eingaben Ex2.+ und x erzeugte Wert wird immer im Cache gespeichert, wie in y . Dies ist referenziell transparent, hat jedoch einen Nebeneffekt , der den letzten von der Funktion berechneten Wert speichert. Jemand anderer kann später kommen und sich Ex0 holen, was ihnen einen kleinen Vorgeschmack auf das gibt, was mit lastResult passiert ist!

  

Eine Randnotiz: Sie können auch argumentieren, dass Ex2.+ nicht referenziell transparent ist, denn obwohl Caching in Bezug auf das Ergebnis der Funktion sicher ist, ist der Nebeneffekt still ignoriert im Falle eines Cache "Hit". Mit anderen Worten, die Einführung eines Caches ändert die Bedeutung des Programms, wenn der Nebeneffekt wichtig ist (daher Norman Ramseys Kommentar )! Wenn Sie diese Definition bevorzugen, muss eine Funktion rein sein, um referenziell transparent zu sein.

Nun, eine Sache, die hier zu beachten ist, ist, dass Ex2.+ sich nicht ändert, wenn wir Ex2.+ zweimal oder mehr hintereinander mit denselben Eingaben aufrufen. Der Nebeneffekt, dass die Methode n aufgerufen wird, entspricht dem Nebeneffekt, dass die Methode nur einmal aufgerufen wird. Daher sagen wir, dass lastResult idempotent ist. Wir könnten es ändern:

%Vor%

Jedes Mal, wenn wir Ex2.+ aufrufen, ändert sich der Verlauf, sodass die Funktion nicht mehr idempotent ist.

Okay, eine Zusammenfassung so weit: Eine reine Funktion ist eine Funktion, die weder liest noch in einen externen Kontext schreibt. Es ist sowohl referentiell transparent als auch nebenwirkungsfrei . Eine Funktion, die aus einem externen Kontext liest, ist nicht mehr referenziell transparent, während eine Funktion, die in einen externen Kontext schreibt, nicht länger frei von Nebenwirkungen ist. Und schließlich hat eine Funktion, die, wenn sie mehrmals mit derselben Eingabe aufgerufen wird, den gleichen Nebeneffekt wie ein einmaliger Aufruf, idempotent . Beachten Sie, dass eine Funktion ohne Nebenwirkungen wie eine reine Funktion auch idempotent ist!

Wie also spielen Veränderlichkeit und Unveränderlichkeit in all dem eine Rolle? Nun, schauen Sie sich Ex3.+ und Ex2 an. Sie führen veränderbare Ex3 s ein. Die Nebenwirkungen von var und Ex2.+ bestehen darin, ihre jeweiligen Ex3.+ s zu mutieren! So gehen Wandelbarkeit und Nebenwirkungen Hand in Hand; Eine Funktion, die nur mit unveränderlichen Daten arbeitet, muss frei von Nebenwirkungen sein. Es könnte immer noch nicht rein sein (das heißt, es könnte nicht referentiell transparent sein), aber es wird zumindest keine Nebenwirkungen erzeugen.

Eine logische Folgefrage könnte sein: "Was sind die Vorteile eines reinen funktionalen Stils?" Die Antwort auf diese Frage ist komplizierter;)

    
mergeconflict 24.08.2012 23:00
quelle
2

"Nein" zum ersten - das eine impliziert das andere, aber nicht das Gegenteil, und ein qualifiziertes "Ja" zum zweiten.

  1. " Ein Ausdruck wird als referenziell transparent bezeichnet, wenn dies möglich ist ersetzt durch seinen Wert, ohne das Verhalten eines Programms zu ändern ". Die Eingabe Unveränderlich legt nahe, dass ein Ausdruck (Funktion) immer denselben Wert hat und daher referenziell transparent ist.

    Allerdings (Mergeconflict hat mich in diesem Punkt freundlicherweise korrigiert), da nicht notwendigerweise immutability erfordert.

  2. Per Definition ist ein Nebeneffekt ein Aspekt einer Funktion ; Das heißt, wenn Sie eine Funktion aufrufen, ändert sich etwas . Unveränderbarkeit ist ein Aspekt von Daten ; Es kann nicht geändert werden. Das Aufrufen einer Funktion bedeutet, dass keine Nebenwirkungen auftreten können. (In Scala ist das auf "keine Änderungen an den unveränderlichen Objekten" beschränkt - der Entwickler hat Verantwortlichkeiten und Entscheidungen).

    Während Nebeneffekt und Unveränderlichkeit nicht dasselbe bedeuten, sind sie eng verwandte Aspekte einer Funktion und der Daten, auf die die Funktion angewendet wird.

Da Scala keine reine funktionale Programmiersprache ist, muss man vorsichtig sein, wenn man die Bedeutung solcher Anweisungen als "unveränderliche Eingabe" betrachtet - der Umfang der Eingabe einer Funktion kann Elemente enthalten, die nicht als Parameter übergeben werden In ähnlicher Weise für die Berücksichtigung von Nebenwirkungen.

    
Richard Sitze 24.08.2012 21:24
quelle
1

Es hängt eher von den spezifischen Definitionen ab, die Sie verwenden (es kann Meinungsverschiedenheiten geben, siehe zum Beispiel Purity vs Referential Transparenz ), aber ich denke, das ist eine vernünftige Interpretation:

Referentielle Transparenz und "Reinheit" sind Eigenschaften von Funktionen / Ausdrücken. Eine Funktion / Ausdruck kann Nebenwirkungen haben oder nicht. Unveränderlichkeit hingegen ist eine Eigenschaft von Objekten, keine Funktionen / Ausdrücke.

Referentielle Transparenz, Nebenwirkungen und Reinheit sind eng miteinander verbunden: "rein" und "referenziell transparent" sind gleichwertig, und diese Begriffe entsprechen dem Fehlen von Nebenwirkungen.

Ein unveränderliches Objekt kann Methoden haben, die nicht referenziell transparent sind: Diese Methoden werden das Objekt selbst nicht verändern (da dies das Objekt veränderbar machen würde), aber andere Nebenwirkungen haben, wie das Ausführen von I / O oder das Manipulieren ihrer ( veränderbare) Parameter.

    
Arnout Engelen 24.08.2012 22:04
quelle

Tags und Links