Was sind die Vorteile der referenziellen Transparenz für einen Programmierer?


18

Was sind die Vorteile der referenziellen Transparenz in der Programmierung ?

RT macht einen der Hauptunterschiede zwischen funktionalem und imperativem Paradigma aus und wird häufig von Befürwortern des funktionalen Paradigmas als klarer Vorteil gegenüber dem imperativen Paradigma verwendet. Bei all ihren Bemühungen erklären diese Befürworter jedoch nie, warum dies für mich als Programmierer von Vorteil ist .

Sicher, sie werden ihre akademischen Erklärungen dazu haben, wie "rein" und "elegant" es ist, aber wie macht es es besser als ein weniger "reiner" Code? Was bringt es mir bei meiner täglichen Programmierung?

Hinweis: Dies ist kein Duplikat von Was ist referenzielle Transparenz? Letztere befasst sich mit dem Thema , was ist RT, während diese Frage seine Vorteile adressses (die möglicherweise nicht so intuitiv sein).




3
Die referenzielle Transparez ermöglicht es Ihnen, Gleichungsgründe zu verwenden , um: 1) die Eigenschaften des Codes zu beweisen und 2) Programme zu schreiben . Es gibt ein paar Bücher über Haskell, in denen die Autoren erläutern sollen, wie Sie von einigen Gleichungen ausgehen können, die eine Funktion erfüllen soll. Wenn Sie nur gleichungsorientierte Überlegungen anstellen, erhalten Sie eine Implementierung dieser Funktion, die daher mit Sicherheit korrekt ist. Nun, wie viel dies in der "täglichen" Programmierung angewendet werden kann, hängt wahrscheinlich vom Kontext ab ...
Bakuriu,

2
@err Mögen Sie Code, der einfacher umzugestalten ist, weil Sie wissen, ob das zweimalige Aufrufen einer Funktion dem Speichern ihres Werts in einer Variablen gleichkommt und diese Variable dann zweimal verwendet? Würden Sie sagen, das ist ein Vorteil für Ihre tägliche Programmierung?
Andres F.

Der Vorteil ist, dass Sie keine Zeit damit verschwenden müssen, über referenzielle Nichttransparenz nachzudenken. Ein bisschen gefällt mir, wie die Vorteile von Variablen sind, dass Sie keine Zeit damit verschwenden müssen, über die Registerzuordnung nachzudenken.
user253751

Antworten:


37

Der Vorteil ist, dass reine Funktionen Ihren Code leichter verständlich machen. Mit anderen Worten, Nebenwirkungen erhöhen die Komplexität Ihres Codes.

Nehmen Sie ein Beispiel für die computeProductPriceMethode.

Ein reines Verfahren würden Sie für eine Produktmenge bitten, eine Währung, usw. Sie wissen , dass , wenn die Methode mit den gleichen Argumenten aufgerufen wird, es wird immer das gleiche Ergebnis.

  • Sie können es sogar zwischenspeichern und die zwischengespeicherte Version verwenden.
  • Sie können es faul machen und den Anruf auf einen Zeitpunkt verschieben, an dem Sie es tatsächlich benötigen, da Sie wissen, dass sich der Wert in der Zwischenzeit nicht ändert.
  • Sie können die Methode mehrmals aufrufen, da Sie wissen, dass sie keine Nebenwirkungen hat.
  • Sie können über die Methode selbst in einer Isolation von der Welt argumentieren, da Sie wissen, dass nur die Argumente benötigt werden.

Eine nicht reine Methode ist komplexer zu verwenden und zu debuggen. Da dies vom Zustand der Variablen abhängt, die keine Argumente sind und möglicherweise geändert werden, kann dies dazu führen, dass beim mehrmaligen Aufrufen unterschiedliche Ergebnisse erzielt werden oder dass nicht dasselbe Verhalten auftritt, wenn die Variablen überhaupt nicht oder zu früh oder zu spät aufgerufen werden.

Beispiel

Stellen Sie sich vor, es gibt eine Methode im Framework, die eine Zahl analysiert:

decimal math.parse(string t)

Es hat keine referenzielle Transparenz, da es von Folgendem abhängt:

  • Die Umgebungsvariable, die das Nummerierungssystem angibt, also Base 10 oder etwas anderes.

  • Die Variable in der mathBibliothek, die die Genauigkeit der zu analysierenden Zahlen angibt. So mit dem Wert 1, den String Parsen "12.3456"geben 12.3.

  • Die Kultur, die die erwartete Formatierung definiert. Zum Beispiel mit fr-FR, Parsing "12.345"geben 12345, weil der Charakter Trennung sollte sein ,, nicht.

Stellen Sie sich vor, wie einfach oder schwierig es wäre, mit einer solchen Methode zu arbeiten. Mit der gleichen Eingabe können Sie je nach dem Moment, in dem Sie die Methode aufrufen, radikal unterschiedliche Ergebnisse erzielen, da irgendwo die Umgebungsvariable geändert oder die Kultur gewechselt oder eine andere Genauigkeit festgelegt wurde. Der nicht deterministische Charakter der Methode würde zu mehr Bugs und mehr Debugging-Alptraum führen. Als Antwort anzurufen math.parse("12345")und zu erhalten 5349, da einige parallele Codes Oktalzahlen parsen, ist nicht schön.

Wie kann man diese offensichtlich kaputte Methode reparieren? Durch die Einführung referentieller Transparenz. Mit anderen Worten, indem Sie den globalen Zustand loswerden und alles an die Parameter der Methode verschieben:

decimal math.parse(string t, base=10, precision=20, culture=cultures.en_us)

Jetzt, da die Methode rein ist, wissen Sie, dass sie unabhängig vom Aufruf der Methode immer das gleiche Ergebnis für dieselben Argumente liefert.


4
Nur ein Zusatz: Die referenzielle Transparenz gilt für alle Ausdrücke in einer Sprache, nicht nur für Funktionen.
Gardenhead

3
Beachten Sie, dass es Einschränkungen gibt, wie transparent Sie sein können. Wenn Sie packet = socket.recv()referenziell transparent machen, verlieren Sie eher den Punkt der Funktion.
Mark

1
Sollte kultur = kulturen.invariant sein. Es sei denn, Sie möchten versehentlich Software erstellen, die nur in den USA ordnungsgemäß funktioniert.
user253751

@immibis: hm, gute frage. Wofür wären die Parsing-Regeln invariant? Entweder sind sie die gleichen wie für en_us, in welchem ​​Fall, warum sie sich die Mühe machen, oder sie entsprechen einem anderen Land, in welchem ​​Fall, welchem ​​und warum dieses stattdessen en_us, oder sie haben ihre spezifischen Regeln, die ohnehin keinem Land entsprechen , das wäre nutzlos. Es gibt wirklich keine „wahre Antwort“ zwischen 12,345.67und 12 345,67: Alle „Standardregeln“ funktionieren für einige Länder und für andere nicht.
Arseni Mourzenko

3
@ArseniMourzenko Es ist im Allgemeinen ein "kleinster gemeinsamer Nenner" und ähnlich der Syntax, die viele Programmiersprachen verwenden (die auch kulturinvariant ist). 12345Analysiert als 12345 12 345oder 12,345oder 12.345ist ein Fehler. 12.345geparst als invariante Gleitkommazahl ergibt sich gemäß der Programmiersprachenkonvention immer ein Wert von 12,345. als Dezimaltrennzeichen. Zeichenfolgen werden nach ihren Unicode-Codepunkten und unter Berücksichtigung der Groß- und Kleinschreibung sortiert. Und so weiter.
user253751

11

Fügen Sie häufig einem Punkt in Ihrem Code einen Haltepunkt hinzu und führen Sie die App im Debugger aus, um herauszufinden, was passiert? Wenn Sie dies tun, liegt dies hauptsächlich daran, dass Sie in Ihren Entwürfen keine referenzielle Transparenz (RT) verwenden. Und so muss der Code ausgeführt werden, um herauszufinden, was er tut.

Der springende Punkt bei RT ist, dass der Code sehr deterministisch ist, dh, Sie können den Code lesen und jedes Mal herausfinden, was er für denselben Satz von Eingaben tut. Sobald Sie anfangen, mutierende Variablen hinzuzufügen, von denen einige über eine einzelne Funktion hinausgehen, können Sie den Code nicht einfach lesen. Solcher Code muss entweder in Ihrem Kopf oder im Debugger ausgeführt werden, um herauszufinden, wie er wirklich funktioniert.

Je einfacher der Code zu lesen und zu begründen ist, desto einfacher ist es, Fehler zu pflegen und zu erkennen. Das spart Zeit und Geld für Sie und Ihren Arbeitgeber.


10
"Sobald Sie anfangen, mutierende Variablen hinzuzufügen, von denen einige über eine einzige Funktion hinausgehen, können Sie den Code nicht nur lesen, sondern müssen ihn entweder im Kopf oder im Debugger ausführen, um herauszufinden, wie er wirklich funktioniert. ": Guter Punkt. Mit anderen Worten, referentielle Transparenz bedeutet nicht nur, dass ein Codeteil immer das gleiche Ergebnis für die gleichen Eingaben liefert, sondern auch, dass das erzeugte Ergebnis der einzige Effekt dieses Codeteils ist, dass es keine andere, verborgene Seite gibt Effekt wie das Ändern einer Variablen, die weit entfernt in einem anderen Modul definiert wurde.
Giorgio

Das ist ein guter Punkt. Ich habe ein kleines Problem damit, je einfacher der Code ist, Argumente zu lesen / zu begründen , da einfacher zu lesen oder zu begründen ein etwas vages und subjektives Attribut von Code ist.
Eyal Roth

Sobald Sie anfangen, mutierende Variablen hinzuzufügen, von denen einige einen Bereich haben, der über eine einzelne Funktion hinausgeht, aber warum wird die Zuweisungsoperation nicht empfohlen, selbst wenn der Variablenbereich lokal funktionsfähig ist?
rahulaga_dev

9

Die Leute werfen den Begriff "einfacher zu überlegen" herum, erklären aber nie, was das bedeutet. Betrachten Sie das folgende Beispiel:

result1 = foo("bar", 12)
// 100 lines of code
result2 = foo("bar", 12)

Sind result1und result2gleich oder verschieden? Ohne referentielle Transparenz haben Sie keine Ahnung. Sie müssen tatsächlich den Body von lesen foo, um sicherzustellen, dass der Body von Funktionen fooaufgerufen wird, und so weiter.

Die Menschen bemerken diese Belastung nicht, weil sie daran gewöhnt sind, aber wenn Sie ein oder zwei Monate lang in einer rein funktionalen Umgebung arbeiten, werden Sie sie spüren, und es ist eine riesige Sache .

Es gibt so viele Verteidigungsmechanismen, die Menschen einsetzen, um den Mangel an referentieller Transparenz zu umgehen. Für mein kleines Beispiel möchte ich vielleicht result1in Erinnerung bleiben , weil ich nicht wüsste, ob es sich ändern würde. Dann habe ich Code mit zwei Zuständen: vorher result1wurde gespeichert und nachher. Mit referentieller Transparenz kann ich es einfach neu berechnen, solange die Neuberechnung nicht zeitaufwändig ist.


1
Sie haben erwähnt, dass Sie mithilfe der referenziellen Transparenz über das Ergebnis der Aufrufe von foo () nachdenken und wissen können, ob result1und result2welche identisch sind. Ein weiterer wichtiger Aspekt ist, dass Sie sich bei foo("bar", 12)referenzieller Transparenz nicht fragen müssen, ob dieser Aufruf irgendwo anders Effekte hervorgerufen hat (einige Variablen setzen - eine Datei löschen - was auch immer).
Giorgio

Die einzige mir vertraute "referentielle Integrität" betrifft relationale Datenbanken.
Mark

1
@ Mark Es ist ein Tippfehler. Karl meinte referentielle Transparenz, wie aus dem Rest seiner Antwort hervorgeht.
Andres F.

6

Ich würde sagen: referentielle Transparenz ist nicht nur gut für die funktionale Programmierung, sondern für alle, die mit Funktionen arbeiten, weil sie dem Prinzip des geringsten Erstaunens folgt.

Sie haben eine Funktion und können besser darüber nachdenken, was sie tut, da Sie keine externen Faktoren berücksichtigen müssen. Für einen bestimmten Eingang ist der Ausgang immer der gleiche. Selbst in meiner imperativen Sprache versuche ich, diesem Paradigma so weit wie möglich zu folgen. Das nächste, was sich daraus automatisch ergibt, ist: kleine, leicht verständliche Funktionen anstelle der grausamen über 1000 Zeilenfunktionen, in denen ich manchmal laufe.

Diese großen Funktionen zaubern und ich habe Angst, sie zu berühren, weil sie auf spektakuläre Weise brechen können.

Reine Funktionen sind also nicht nur etwas für die funktionale Programmierung, sondern für jedes Programm.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.