Reine Funktionen: Bedeutet „Keine Nebenwirkungen“ „Immer gleiche Ausgabe bei gleicher Eingabe“?

84

Die zwei Bedingungen, die eine Funktion purewie folgt definieren, sind wie folgt:

  1. Keine Nebenwirkungen (dh nur Änderungen des lokalen Bereichs sind zulässig)
  2. Geben Sie immer den gleichen Ausgang zurück, wenn Sie den gleichen Eingang haben

Wenn die erste Bedingung immer wahr ist, gibt es Zeiten, in denen die zweite Bedingung nicht wahr ist?

Dh ist es wirklich nur bei der ersten Bedingung notwendig?

Magnus
quelle
3
Ihre Räumlichkeiten sind schlecht spezifiziert. "Input" ist zu breit. Man kann sich vorstellen, dass zwei Arten von Eingaben haben. Ihre Argumente und "Umwelt" / "Kontext". Eine Funktion, die die Systemzeit zurückgibt, kann als rein angesehen werden (auch wenn dies nicht der Fall ist), wenn Sie nicht zwischen diesen beiden Arten von Eingaben unterscheiden.
Alexander - Reinstate Monica
4
@Alexander: Im Kontext von "reine Funktion" bedeutet "Eingabe" üblicherweise die Parameter / Argumente, die explizit übergeben werden (nach welchem ​​Mechanismus auch immer die Programmiersprache verwendet). Das ist Teil der Definition von "reiner Funktion". Aber Sie haben Recht, es ist wichtig, die Definition zu beachten.
Sleske
3
Triviales Gegenbeispiel: Gibt den Wert einer globalen Variablen zurück. Keine Nebenwirkungen (das Globale wird immer nur gelesen!), Aber jedes Mal möglicherweise unterschiedliche Ergebnisse. (Wenn Sie Globals nicht mögen, geben Sie die Adresse einer lokalen Variablen zurück, die zur Laufzeit vom Aufrufstapel abhängt.)
Peter - Monica am
2
Sie müssen Ihre Definition von "Nebenwirkungen" erweitern. Sie sagen , dass eine reine Methode nicht produzieren Nebenwirkungen, aber Sie müssen auch zur Kenntnis , dass eine reine Methode nicht verbrauchen Nebenwirkungen an anderer Stelle erzeugt.
Eric Lippert
2
@sleske Vielleicht allgemein verstanden, aber das Fehlen dieser Unterscheidung ist die genaue Ursache für die Verwirrung von OP.
Alexander - Reinstate Monica

Antworten:

114

Hier einige Gegenbeispiele, die den äußeren Umfang nicht verändern, aber dennoch als unrein gelten:

  • function a() { return Date.now(); }
  • function b() { return window.globalMutableVar; }
  • function c() { return document.getElementById("myInput").value; }
  • function d() { return Math.random(); } (was zwar das PRNG ändert, aber nicht als beobachtbar angesehen wird)

Der Zugriff auf nicht konstante nicht lokale Variablen reicht aus, um die zweite Bedingung verletzen zu können.

Ich halte die beiden Reinheitsbedingungen immer für komplementär:

  • Die Ergebnisbewertung darf keine Auswirkungen auf den Nebenzustand haben
  • Das Bewertungsergebnis darf nicht durch den Nebenzustand beeinflusst werden

Der Begriff Nebenwirkung bezieht sich nur auf die erste, die Funktion, die den nicht lokalen Zustand modifiziert. Manchmal werden Lesevorgänge jedoch auch als Nebenwirkungen betrachtet: Wenn es sich um Vorgänge handelt, die auch das Schreiben umfassen, selbst wenn ihr Hauptzweck darin besteht, auf einen Wert zuzugreifen. Beispiele hierfür sind das Erzeugen einer Pseudozufallszahl, die den internen Zustand des Generators ändert, das Lesen aus einem Eingangsstrom, der die Leseposition vorverlegt, oder das Lesen von einem externen Sensor, der einen Befehl "Messung durchführen" beinhaltet.

Bergi
quelle
1
Danke Bergi. Aus irgendeinem Grund dachte ich , Nebenwirkungen, die das Lesen Variablen außerhalb des lokalen Bereich, aber ich denke , es ist nur ein Nebeneffekt ist , wenn er schreibt solche externen Variablen.
Magnus
17
Wenn prompt("you choose")es keine Nebenwirkungen gibt, sollten wir einen Schritt zurücktreten und die Bedeutung der Nebenwirkungen klären.
Holger
1
@Magnus Ja, genau das bedeutet Effekt . Ich werde versuchen, auch in meiner Antwort zu klären, ich habe nicht so viel Aufmerksamkeit erwartet und möchte eine Antwort machen, die Dutzende von Stimmen verdient :-)
Bergi
2
Nach allem, was Sie wissen, gibt Math.random () eine thermische Diode zurück. Es ist eigentlich nicht spezifiziert, ein schlechtes RNG zu verwenden.
Joshua
1
Von den beiden Bedingungen habe ich die erstere als "Effekte" bezeichnet, während die letztere als "Koeffizienten" bezeichnet wird. Beide sind "Nebenwirkungen" und unrein. f (Koeffizienten, Eingabe) -> Effekte, Ausgabe Koeffizienten sind Eingaben, die sich aus den Änderungen in der weiteren Umgebung ergeben. Effekte werden ausgegeben, die die weitere Umgebung verändern. Elm und Clojurescrips arbeiten beispielsweise mit diesem Modell neu.
30

Die "normale" Art zu formulieren, was eine reine Funktion ist, ist die referenzielle Transparenz . Eine Funktion ist rein, wenn sie referenziell transparent ist .

Referentielle Transparenz bedeutet ungefähr, dass Sie den Aufruf der Funktion an jeder Stelle im Programm durch ihren Rückgabewert ersetzen können oder umgekehrt, ohne die Bedeutung des Programms zu ändern.

Wenn beispielsweise printfCs referenziell transparent wären , sollten diese beiden Programme dieselbe Bedeutung haben:

printf("Hello");

und

5;

und alle folgenden Programme sollten dieselbe Bedeutung haben:

5 + 5;

printf("Hello") + 5;

printf("Hello") + printf("Hello");

Weil printfdie Anzahl der geschriebenen Zeichen zurückgibt, in diesem Fall 5.

Bei voidFunktionen wird es noch deutlicher . Wenn ich eine Funktion habe void foo, dann

foo(bar, baz, quux);

sollte das gleiche sein wie

;

Das heißt, da foonichts zurückgegeben wird, sollte ich es durch nichts ersetzen können, ohne die Bedeutung des Programms zu ändern.

Es ist also klar, dass weder printfnoch fooreferenziell transparent sind und somit keiner von ihnen rein ist. Tatsächlich kann eine voidFunktion niemals referenziell transparent sein, es sei denn, es handelt sich um ein No-Op.

Ich finde diese Definition viel einfacher zu handhaben als die, die Sie gegeben haben. Sie können es auch in jeder gewünschten Granularität anwenden: Sie können es auf einzelne Ausdrücke, Funktionen und ganze Programme anwenden. So können Sie beispielsweise über eine Funktion wie diese sprechen:

func fib(n):
    return memo[n] if memo.has_key?(n)
    return 1 if n <= 1
    return memo[n] = fib(n-1) + fib(n-2)

Wir können die Ausdrücke analysieren, aus denen die Funktion besteht, und leicht den Schluss ziehen, dass sie nicht referenziell transparent und daher nicht rein sind, da sie eine veränderbare Datenstruktur verwenden, nämlich das memoArray. Wir können jedoch auf die Funktion auch einen Blick und sehen , dass es ist referentiell transparent und somit rein. Dies wird manchmal als äußere Reinheit bezeichnet , dh eine Funktion, die der Außenwelt rein erscheint, aber intern unrein implementiert wird.

Solche Funktionen sind immer noch nützlich, da die Verunreinigung zwar alles um sie herum infiziert, die externe reine Schnittstelle jedoch eine Art "Reinheitsbarriere" bildet, bei der die Verunreinigung nur die drei Zeilen der Funktion infiziert, aber nicht in den Rest des Programms gelangt . Diese drei Zeilen sind viel einfacher auf Korrektheit zu analysieren als das gesamte Programm.

Jörg W Mittag
quelle
2
Diese Verunreinigung wirkt sich auf das gesamte Programm aus, sobald Sie Parallelität haben.
R .. GitHub STOP HELPING ICE
@R .. Können Sie sich vorstellen, wie Parallelität die beschriebene Fibonacci-Funktion von außen unrein machen könnte? Ich kann nicht Das Schreiben in memo[n]ist idempotent, und das Nichtlesen verschwendet lediglich CPU-Zyklen.
Brilliand
Ich stimme euch beiden zu. Verunreinigungen können zu Parallelitätsproblemen führen, in diesem speziellen Fall jedoch nicht.
Jörg W Mittag
@R .. Es ist nicht schwer, sich eine Parallelitäts-fähige Version vorzustellen.
user253751
1
@Brilliand Erstellt beispielsweise memo[n] = ...zuerst einen Wörterbucheintrag und speichert dann den Wert darin. Dadurch bleibt ein Fenster, in dem ein anderer Thread einen nicht initialisierten Eintrag sehen kann.
user253751
12

Es scheint mir, dass die zweite Bedingung, die Sie beschrieben haben, eine schwächere Einschränkung ist als die erste.

Lassen Sie mich ein Beispiel geben. Angenommen, Sie haben eine Funktion zum Hinzufügen einer Funktion, die sich auch an der Konsole anmeldet:

function addOneAndLog(x) {
  console.log(x);
  return x + 1;
}

Die zweite von Ihnen angegebene Bedingung ist erfüllt: Diese Funktion gibt immer dieselbe Ausgabe zurück, wenn dieselbe Eingabe gegeben wird. Dies ist jedoch keine reine Funktion, da sie den Nebeneffekt der Protokollierung an der Konsole enthält.

Eine reine Funktion ist streng genommen eine Funktion, die die Eigenschaft der referenziellen Transparenz erfüllt . Mit dieser Eigenschaft können wir eine Funktionsanwendung durch den von ihr erzeugten Wert ersetzen, ohne das Verhalten des Programms zu ändern.

Angenommen, wir haben eine Funktion, die einfach Folgendes hinzufügt:

function addOne(x) {
  return x + 1;
}

Wir können ersetzen addOne(5)mit 6überall in unserem Programm und wird sich nichts ändern.

Im Gegensatz dazu können wir nirgendwo in unserem Programm durch addOneAndLog(x)den Wert ersetzen 6, ohne das Verhalten zu ändern, da der erste Ausdruck dazu führt, dass etwas in die Konsole geschrieben wird, während der zweite dies nicht tut.

Wir betrachten jedes dieser zusätzlichen Verhaltensweisen, addOneAndLog(x)die neben der Rückgabe der Ausgabe auftreten, als Nebeneffekt .

TheInnerLight
quelle
"Es scheint mir, dass die zweite Bedingung, die Sie beschrieben haben, eine schwächere Einschränkung ist als die erste." Nein, die beiden Bedingungen sind logisch unabhängig.
Sleske
@sleske du liegst falsch. Ich habe klare Definitionen für die Begriffe rein und Nebenwirkung gegeben. Innerhalb dieser Einschränkungen gibt es nichts, was eine Funktion ohne Nebenwirkungen außer der Ausgabe derselben Ausgabe für eine bestimmte Eingabe gibt. Ich habe jedoch Beispiele angeführt, bei denen die zweite Bedingung ohne die erste erfüllt werden kann. Das fundamentale Konzept zum Verständnis des Begriffs Reinheit ist referenzielle Transparenz.
TheInnerLight
Kleiner Tippfehler: Es gibt nichts, was eine Funktion ohne Nebenwirkungen tun kann, außer dieselbe Ausgabe für eine bestimmte Eingabe zurückzugeben.
TheInnerLight
Was ist mit so etwas wie der Rückgabe der aktuellen Zeit? Das hat keine Nebenwirkungen, gibt aber für denselben Eingang einen anderen Ausgang zurück. Oder allgemeiner gesagt, jede Funktion, bei der das Ergebnis nicht nur von den Eingabeparametern abhängt, sondern auch von einer (veränderbaren) globalen Variablen.
Sleske
2
Es scheint, dass Sie eine andere Definition von "Nebenwirkung" verwenden als die üblicherweise verwendete. Ein Nebeneffekt wird üblicherweise als "beobachtbarer Effekt neben der Rückgabe eines Wertes" oder "beobachtbare Zustandsänderung" definiert - siehe z. B. Wikipedia , diesen Beitrag zu softwareengineering.SE . Sie haben völlig Recht, das Date.now()ist nicht rein / referenziell transparent, aber nicht, weil es Nebenwirkungen hat, sondern weil sein Ergebnis von mehr als nur seiner Eingabe abhängt.
Sleske
7

Es kann eine Zufallsquelle von außerhalb des Systems geben. Angenommen, ein Teil Ihrer Berechnung enthält die Raumtemperatur. Die Ausführung der Funktion führt dann jedes Mal zu unterschiedlichen Ergebnissen, abhängig vom zufälligen externen Element der Raumtemperatur. Der Status wird durch Ausführen des Programms nicht geändert.

Jedenfalls kann ich mir nur vorstellen.

user3340459
quelle
3
Meiner Meinung nach sind diese "Zufälligkeiten von außerhalb des Systems" eine Form von Nebenwirkung. Funktionen mit diesen Verhaltensweisen sind keine "Reinheiten".
Joseph M. Dion
2

Das Problem bei FP-Definitionen ist, dass sie sehr künstlich sind. Jede Bewertung / Berechnung hat Nebenwirkungen auf den Bewerter. Es ist theoretisch wahr. Eine Ablehnung zeigt nur, dass FP-Apologeten Philosophie und Logik ignorieren: Eine "Bewertung" bedeutet, den Zustand einer intelligenten Umgebung (Maschine, Gehirn usw.) zu ändern. Dies ist die Art des Bewertungsprozesses. Keine Änderungen - keine "Kalküle". Der Effekt kann sehr gut sichtbar sein: Erhitzen der CPU oder deren Ausfall, Herunterfahren des Motherboards bei Überhitzung usw.

Wenn Sie über referenzielle Transparenz sprechen, sollten Sie verstehen, dass Informationen über diese Transparenz für den Menschen als Schöpfer des gesamten Systems und Inhaber semantischer Informationen verfügbar sind und möglicherweise nicht für den Compiler verfügbar sind. Beispielsweise kann eine Funktion eine externe Ressource lesen und hat eine E / A-Monade in ihrer Signatur, gibt jedoch immer den gleichen Wert zurück (z. B. das Ergebnis von current_year > 0). Der Compiler weiß nicht, dass die Funktion immer das gleiche Ergebnis zurückgibt, daher ist die Funktion unrein, hat jedoch eine referenziell transparente Eigenschaft und kann durch eine TrueKonstante ersetzt werden.

Um solche Ungenauigkeiten zu vermeiden, sollten wir mathematische Funktionen und "Funktionen" in Programmiersprachen unterscheiden. Funktionen in Haskell sind immer unrein und die Definition der damit verbundenen Reinheit ist immer sehr bedingt: Sie laufen auf realer Hardware mit realen Nebenwirkungen und physikalischen Eigenschaften, was für mathematische Funktionen falsch ist. Dies bedeutet, dass das Beispiel mit der Funktion "printf" völlig falsch ist.

Aber nicht alle mathematischen Funktionen sind auch rein: Jede Funktion, die t(Zeit) als Parameter hat, kann unrein sein: Sie tenthält alle Effekte und die stochastische Natur der Funktion: Im allgemeinen Fall haben Sie ein Eingangssignal und keine Ahnung von tatsächlichen Werten sei sogar ein Geräusch.

Paul-AG
quelle
2

Wenn die erste Bedingung immer wahr ist, gibt es Zeiten, in denen die zweite Bedingung nicht wahr ist?

Ja

Betrachten Sie unten ein einfaches Code-Snippet

public int Sum(int a, int b) {
    Random rnd = new Random();
    return rnd.Next(1, 10);
}

Dieser Code gibt eine zufällige Ausgabe für denselben gegebenen Satz von Eingaben zurück - hat jedoch keine Nebenwirkungen.

Der Gesamteffekt der beiden Punkte Nr. 1 und Nr. 2, die Sie in Kombination erwähnt haben, bedeutet: Zu jedem Zeitpunkt, wenn die Funktion Summit derselben E / A durch das Ergebnis in einem Programm ersetzt wird, ändert sich die Gesamtbedeutung des Programms nicht . Dies ist nichts anderes als referenzielle Transparenz .

rahulaga_dev
quelle
In diesem Fall wird die erste Bedingung jedoch nicht überprüft: Das Schreiben auf die Konsole wird als Nebeneffekt angesehen, da es den Status der Maschine selbst ändert.
Rechtes Bein
@ Rightleg danke für den Hinweis. Irgendwie habe ich OP völlig anders verstanden. korrigierte Antwort.
rahulaga_dev
2
Ändert es nicht den Zustand des Zufallsgenerators?
Eric Duminil
1
Das Erzeugen einer Zufallszahl ist selbst ein Nebeneffekt, es sei denn, der Zustand des Zufallszahlengenerators wird explizit angegeben, wodurch die Funktion die Bedingung 2 erfüllen würde.
TheInnerLight
1
rndentgeht der Funktion nicht, daher spielt die Tatsache, dass sich ihr Status ändert, keine Rolle für die Reinheit der Funktion, aber die Tatsache, dass der RandomKonstruktor die aktuelle Zeit als Startwert verwendet, bedeutet, dass es andere "Eingaben" als aund gibt b.
Sneftel