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.
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 ein factorial(5)
Ausdruck im Programm gegeben ist, sollte er immer das gleiche "Ergebnis" liefern. Wenn also ein bestimmtes factorial(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.
def factorial(n):
if n == 1:
return 1
return n * factorial(n - 1)
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.
def first(y, z):
return y
def second(x):
return second(x)
first(2, second(3)) # result depends on eval. model
Im obigen Code first
und second
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:
sub foo {
my $x = shift;
return $x + $y; # y is dynamic scope var
}
sub a {
local $y = 10;
return &foo(50); # expanded to 60
}
sub b {
local $y = 20;
return &foo(50); # expanded to 70
}
Der dynamische Bereich führt in die Irre, weil er es einem leicht macht zu denken, dass dies x
der einzige Input ist foo
, wenn es in Wirklichkeit so ist x
und y
. 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 definieren foo(x)
, definieren foo(x, y)
und übergeben wir y
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?
def foo():
global y
y = y + 1
return y
y = 10
foo() # yields 11
foo() # yields 12
Die foo
Prozedur bricht RT, weil es Neudefinitionen gibt. Das heißt, wir haben y
in einem Punkt definiert und später dasselbe neu definiert y
. Im obigen Perl-Beispiel sind die y
s unterschiedliche Bindungen, obwohl sie denselben Buchstabennamen "y" haben. Hier sind die y
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 Sie input -> ouput
im Kontext eine state
, 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.
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?