Siccome capisco approssimativamente il modello di sostituzione (con trasparenza referenziale (RT)), puoi decomporre una funzione nelle sue parti più semplici. Se l'espressione è RT, puoi decomporre l'espressione e ottenere sempre lo stesso risultato.
Sì, l'intuizione è giusta. Ecco alcuni suggerimenti per essere più precisi:
Come hai detto, qualsiasi espressione RT dovrebbe avere un single
"risultato". Cioè, data factorial(5)
un'espressione nel programma, dovrebbe sempre produrre lo stesso "risultato". Quindi, se un certo factorial(5)
è nel programma e produce 120, dovrebbe sempre produrre 120 indipendentemente da quale "ordine di passi" viene espanso / calcolato - indipendentemente dal tempo .
Esempio: la factorial
funzione.
def factorial(n):
if n == 1:
return 1
return n * factorial(n - 1)
Ci sono alcune considerazioni con questa spiegazione.
Prima di tutto, tieni presente che i diversi modelli di valutazione (vedi ordine applicativo vs. ordine normale) possono produrre "risultati" diversi per la stessa espressione RT.
def first(y, z):
return y
def second(x):
return second(x)
first(2, second(3)) # result depends on eval. model
Nel codice sopra, first
e second
sono referenzialmente trasparenti, eppure, l'espressione alla fine produce diversi "risultati" se valutata in ordine normale e ordine applicativo (in quest'ultimo caso, l'espressione non si ferma).
.... che porta all'uso del "risultato" tra virgolette. Poiché non è necessario arrestare un'espressione, potrebbe non produrre un valore. Quindi usare "risultato" è piuttosto sfocato. Si può dire che un'espressione RT produce sempre lo stesso computations
in un modello di valutazione.
Terzo, potrebbe essere richiesto di vedere due foo(50)
apparire nel programma in posizioni diverse come espressioni diverse, ognuna con i propri risultati che potrebbero differire l'una dall'altra. Ad esempio, se il linguaggio consente un ambito dinamico, entrambe le espressioni, sebbene lessicalmente identiche, sono diverse. 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
}
L'ambito dinamico inganna perché rende facile per uno pensare che x
sia l'unico input per foo
, quando in realtà lo è x
e y
. Un modo per vedere la differenza è trasformare il programma in un programma equivalente senza ambito dinamico, ovvero passare esplicitamente i parametri, quindi invece di definire foo(x)
, definiamo foo(x, y)
e passiamo y
esplicitamente nei chiamanti.
Il punto è che siamo sempre sotto una function
mentalità: dato un certo input per un'espressione, ci viene dato un "risultato" corrispondente. Se diamo lo stesso input, dovremmo sempre aspettarci lo stesso "risultato".
Ora, che dire del seguente codice?
def foo():
global y
y = y + 1
return y
y = 10
foo() # yields 11
foo() # yields 12
La foo
procedura interrompe RT perché ci sono ridefinizioni. Cioè, abbiamo definito y
in un punto, e in seguito, ridefinito lo stesso y
. Nell'esempio perl di cui sopra, le y
s sono associazioni diverse sebbene condividano lo stesso nome di lettera "y". Qui le y
s sono effettivamente le stesse. Ecco perché diciamo che il (ri) assegnamento è una meta operazione: stai infatti cambiando la definizione del tuo programma.
All'incirca, le persone di solito descrivono la differenza come segue: in un'impostazione libera da effetti collaterali, hai una mappatura da input -> output
. In un'ambientazione "imperativa", hai input -> ouput
nel contesto di un state
che può cambiare nel tempo.
Ora, invece di sostituire semplicemente le espressioni con i loro valori corrispondenti, si devono anche applicare trasformazioni state
a ciascuna operazione che lo richiede (e, naturalmente, le espressioni possono consultare lo stesso state
per eseguire calcoli).
Quindi, se in un programma privo di effetti collaterali tutto ciò che dobbiamo sapere per calcolare un'espressione è il suo input individuale, in un programma imperativo, dobbiamo conoscere gli input e l'intero stato, per ogni fase computazionale. Il ragionamento è il primo a subire un duro colpo (ora, per eseguire il debug di una procedura problematica, sono necessari input e core dump). Alcuni trucchi sono resi poco pratici, come la memoizzazione. Ma anche la concorrenza e il parallelismo diventano molto più difficili.
RT
ti impedisce di usaresubstitution model.
Il grosso problema di non poterlo usaresubstitution model
è il potere di usarlo per ragionare su un programma?