Per aggiungere altri esempi, in una programmazione imperativa (OOP), è necessaria la mutevolezza per rendere l'ordine più importante, mentre le variabili non locali - in presenza di mutabilità - aggiungono alla confusione.
Innanzitutto riguardo alla mutabilità: diciamo che abbiamo uno Stack, che:
someStack.Push ( 400 );
someStack.Push ( 500 );
// stack now has (at least) 400, 500 <-- top of stack
var answer = someStack.Pop () / someStack.Pop ();
L'ordine di valutazione degli operandi di /
(numeratore vs denominatore) è significativo qui perché il primo a Pop()
otterrà 500 e il secondo 400. Quindi, un ordine calcolerà 400/500 e l'altro l'ordine calcolerà 500/400.
A proposito, l'ordine di valutazione degli operandi non è definito in un linguaggio come, C in modo che il compilatore possa scegliere il modo più efficiente. È quindi un onere per il programmatore scrivere codice a cui non interessa l'ordinamento. In C dovremmo usare i punti di sequenza per solidificare l'ordine:
float numerator = someStack.Pop (); // happens first because of ";"
float denominator = someStack.Pop (); // happens second
float answer = numerator / denominator; // order of operand evaluation doesn't matter
// since they're just simple variables.
Successivamente, le variabili non locali, in combinazione con la mutevolezza, consentono la condivisione di dati (come quella pila) senza che la condivisione sia necessariamente ovvia / visibile dalla lettura di una riga di codice sorgente. Quindi, nell'esempio di @ Christope,
f(g(x), h(y))
Come non sappiamo leggendo questa riga di codice, cosa possono fare o potrebbero fare g
e h
; ognuno potrebbe fare un someStack.Pop ()
, modificando questo stack di esempio, se lo stack a cui fa riferimento someStack
fosse accessibile ad entrambi in alcune variabili non locali, condivise o globali. L'ordine di valutazione determinerebbe quindi se g
vede 500 e h
400, o viceversa.
Alcuni linguaggi di programmazione (diversi da C) definiranno un ordine di valutazione degli operandi (ad esempio da sinistra a destra) per ridurre la confusione.
In pura FP, non abbiamo la mutabilità - quindi, tutte le variabili, una volta definite, rimangono costanti, anche se accessibili tramite variabili non locali. Se la mutazione è preclusa, tutti gli usi delle stesse variabili vedranno gli stessi valori. Pertanto, gli esempi di cui sopra non possono essere codificati. La modifica dello stack (push o pop) produrrebbe una nuova struttura di dati dello stack, lasciando l'originale intatto, se qualcuno potesse guardarlo.
Si noti che questo è molto diverso dalle dichiarazioni const in linguaggi come C ++ e JavaScript o finale in Java o readonly in C #: in questi linguaggi, const / final / readonly significa solo che la variabile stessa non può essere aggiornata, ma un struttura dei dati i punti variabili sono ancora aperti per la mutazione ! Nella pura FP, non solo è una variabile immutabile, ma lo è anche l'intera struttura dati a cui si riferisce.
Si noti inoltre che l'ordine di valutazione degli operandi è un concetto indipendente dalla precedenza degli operatori.
La precedenza degli operatori è un concetto sintattico che è (generalmente) non ambiguo per definizione della grammatica di una lingua: a+b*c
significa bind b
e c
a *
e quindi bind a
e il risultato da *
a +
.
Mentre l'ordine di valutazione degli operandi avviene dopo che è stato stabilito il binding definito in precedenza (in a+b*c
: singolarmente gli operandi a
, b
e c
possono ancora essere valutati in qualsiasi ordine nel linguaggio C) .
Il costo di una rigida valutazione da sinistra a destra degli operandi è un'efficienza occasionale, nel richiedere più variabili temporanee (il compilatore ha introdotto il livello intermedio) e più temps che sono attivi attraverso le chiamate. Ad esempio, in a+f()
, se il compilatore pensa che a
possa essere modificato invocando f()
allora qui dovrebbe copiare a
in una temp prima di chiamare f()
in una sinistra-a-destra lingua, mentre C invoca f()
prima, quindi usa a
direttamente, sfruttando il suo ordine di valutazione operand non specificato.