Die funktionale Programmierung in Scala erklärt die Auswirkungen eines Nebeneffekts auf die Aufhebung der referenziellen Transparenz:
Nebeneffekt, der eine Verletzung der referenziellen Transparenz impliziert.
Ich habe einen Teil von SICP gelesen , in dem die Verwendung des „Substitutionsmodells“ zur Bewertung eines Programms erörtert wird.
Da ich das Substitutionsmodell mit referentieller Transparenz (RT) grob verstehe, können Sie eine Funktion in ihre einfachsten Teile zerlegen. Wenn der Ausdruck RT ist, können Sie den Ausdruck zerlegen und immer das gleiche Ergebnis erhalten.
Wie im obigen Zitat angegeben, kann / wird die Verwendung von Nebenwirkungen das Substitutionsmodell jedoch zerstören.
Beispiel:
val x = foo(50) + bar(10)
Wenn foo
und bar
keine Nebenwirkungen auftreten, gibt die Ausführung einer der beiden Funktionen immer das gleiche Ergebnis zurück x
. Wenn sie jedoch Nebenwirkungen haben, ändern sie eine Variable, die das Substitutionsmodell stört.
Ich fühle mich mit dieser Erklärung wohl, aber ich verstehe sie nicht ganz.
Bitte korrigieren Sie mich und füllen Sie alle Lücken in Bezug auf Nebenwirkungen aus, die die RT brechen, und besprechen Sie auch die Auswirkungen auf das Substitutionsmodell.
quelle
RT
Wenn Sie also brechen, können Sie das nicht verwenden.substitution model.
Das große Problem , wenn Sie das nicht verwenden können,substitution model
ist die Fähigkeit, es zu verwenden, um über ein Programm nachzudenken?Stellen Sie sich vor, Sie versuchen eine Mauer zu bauen und erhalten eine Auswahl an Kisten in verschiedenen Größen und Formen. Sie müssen ein bestimmtes L-förmiges Loch in die Wand füllen. Sollten Sie nach einer L-förmigen Box suchen oder können Sie zwei gerade Boxen der entsprechenden Größe ersetzen?
In der Funktionswelt lautet die Antwort, dass beide Lösungen funktionieren. Wenn Sie Ihre funktionale Welt aufbauen, müssen Sie niemals die Kisten öffnen, um zu sehen, was sich darin befindet.
In der imperativen Welt ist es gefährlich, eine Wand zu bauen, ohne den Inhalt jeder Schachtel zu überprüfen und ihn mit dem Inhalt jeder anderen Schachtel zu vergleichen:
Ich denke, ich werde aufhören, bevor ich Ihre Zeit mit unwahrscheinlicheren Metaphern verschwende, aber ich hoffe, dass der Punkt gemacht wird; Funktionsbausteine enthalten keine versteckten Überraschungen und sind vollständig vorhersehbar. Da Sie immer kleinere Blöcke mit der richtigen Größe und Form verwenden können, um einen größeren zu ersetzen, und es keinen Unterschied zwischen zwei Feldern mit derselben Größe und Form gibt, haben Sie referenzielle Transparenz. Bei zwingenden Ziegeln reicht es nicht aus, etwas in der richtigen Größe und Form zu haben - man muss wissen, wie der Ziegel gebaut wurde. Nicht referenziell transparent.
In einer reinen funktionalen Sprache müssen Sie lediglich die Signatur einer Funktion sehen, um zu wissen, was sie tut. Natürlich könnten Sie nach innen schauen , wie gut es führt zu sehen, aber Sie nicht haben zu sehen.
In einer imperativen Sprache weiß man nie, welche Überraschungen sich darin verbergen könnten.
quelle
(a, b) -> a
nur diefst
Funktion sein kann und dass eine Funktion des Typsa -> a
nur dieidentity
Funktion sein kann, aber Sie können beispielsweise nicht unbedingt etwas über eine Funktion des Typs sagen(a, a) -> a
.Ja, die Intuition ist ganz richtig. Hier sind einige Hinweise, um genauer zu werden:
Wie Sie sagten, sollte jeder RT-Ausdruck ein
single
"Ergebnis" haben. Das heißt, wenn einfactorial(5)
Ausdruck im Programm gegeben ist, sollte er immer das gleiche "Ergebnis" liefern. Wenn also ein bestimmtesfactorial(5)
Element im Programm enthalten ist und 120 ergibt, sollte es immer 120 ergeben, unabhängig davon, welche "Schrittreihenfolge" es erweitert / berechnet - unabhängig von der Zeit .Beispiel: die
factorial
Funktion.Bei dieser Erklärung gibt es einige Überlegungen.
Beachten Sie zunächst, dass die verschiedenen Bewertungsmodelle (siehe anwendbare vs. normale Reihenfolge) unterschiedliche "Ergebnisse" für dieselbe RT-Expression liefern können.
Im obigen Code
first
undsecond
sind referenziell transparent, und dennoch liefert der Ausdruck am Ende unterschiedliche "Ergebnisse", wenn er in normaler und anwendbarer Reihenfolge ausgewertet wird (unter letzterer hört der Ausdruck nicht auf)..... was zur Verwendung von "Ergebnis" in Anführungszeichen führt. Da ein Ausdruck nicht angehalten werden muss, wird möglicherweise kein Wert erzeugt. Die Verwendung von "Ergebnis" ist also etwas verschwommen. Man kann sagen, dass ein RT-Ausdruck
computations
unter einem Bewertungsmodell immer dasselbe ergibt .Drittens kann es erforderlich sein, zwei
foo(50)
im Programm an verschiedenen Stellen als unterschiedliche Ausdrücke zu sehen - jeder liefert seine eigenen Ergebnisse, die sich voneinander unterscheiden können. Wenn die Sprache beispielsweise einen dynamischen Bereich zulässt, sind beide Ausdrücke unterschiedlich, obwohl sie lexikalisch identisch sind. In Perl:Der dynamische Bereich führt in die Irre, weil er es einem leicht macht zu denken, dass dies
x
der einzige Input istfoo
, wenn es in Wirklichkeit so istx
undy
. Eine Möglichkeit, den Unterschied zu erkennen, besteht darin, das Programm in ein äquivalentes Programm ohne dynamischen Bereich umzuwandeln, dh die Parameter explizit zu übergeben. Anstatt zu definierenfoo(x)
, definierenfoo(x, y)
und übergeben wiry
die Aufrufer explizit.Der Punkt ist, wir sind immer unter einer
function
Denkweise: Wenn wir eine bestimmte Eingabe für einen Ausdruck erhalten, erhalten wir ein entsprechendes "Ergebnis". Wenn wir den gleichen Input geben, sollten wir immer das gleiche "Ergebnis" erwarten.Was ist nun mit dem folgenden Code?
Die
foo
Prozedur bricht RT, weil es Neudefinitionen gibt. Das heißt, wir habeny
in einem Punkt definiert und später dasselbe neu definierty
. Im obigen Perl-Beispiel sind diey
s unterschiedliche Bindungen, obwohl sie denselben Buchstabennamen "y" haben. Hier sind diey
s eigentlich gleich. Deshalb sagen wir, dass (Neu-) Zuweisung eine Metaoperation ist: Sie ändern tatsächlich die Definition Ihres Programms.In der Regel wird der Unterschied wie folgt dargestellt: In einer Umgebung ohne Nebenwirkungen haben Sie eine Zuordnung von
input -> output
. In einer "imperativen" Einstellung haben Sieinput -> ouput
im Kontext einestate
, die sich im Laufe der Zeit ändern kann.Anstatt nur die entsprechenden Werte durch Ausdrücke zu ersetzen, müssen
state
bei jeder Operation, die dies erfordert , Transformationen auf die angewendet werden (und natürlich können Ausdrücke diese konsultieren,state
um Berechnungen durchzuführen).Wenn also in einem nebenwirkungsfreien Programm alles, was wir wissen müssen, um einen Ausdruck zu berechnen, seine individuelle Eingabe ist, müssen wir in einem imperativen Programm die Eingaben und den gesamten Zustand für jeden Rechenschritt kennen. Das Denken ist das erste, das einen großen Schlag erleidet (um ein problematisches Verfahren zu debuggen, benötigen Sie die Eingabe und den Core-Dump). Bestimmte Tricks werden unpraktisch gemacht, wie das Auswendiglernen. Parallelität und Parallelität werden jedoch auch viel schwieriger.
quelle