Nella programmazione funzionale, cosa significa "L'ordine di valutazione non ha importanza" quando la funzione è pura?

3

Nel contesto delle pure funzioni in FP, oltre ai numerosi vantaggi menzionati come facili da ragionare, testabilità dice anche che " l'ordine di valutazione non ha importanza " poiché l'output rimane lo stesso per un dato input. Probabilmente sono di origini OO e trovo difficile capire cosa significhi in realtà. Sarebbe bello se qualcuno potesse spiegarlo chiaramente con qualche esempio (preferibilmente confrontandolo con la funzione impura dove l'ordine conta)

    
posta Rahul Agarwal 08.09.2018 - 21:58
fonte

3 risposte

6

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.

    
risposta data 09.09.2018 - 01:38
fonte
6

Ciò significa che l'assenza di effetti collaterali assicura che le funzioni possano essere valutate in un ordine diverso e dare lo stesso risultato.

Esempio:

  • In un f(g(x), h(y)) funzionale darà lo stesso risultato, sia che g(x) sia valutato per primo o h(y) sia valutato per primo, perché il loro rispettivo risultato dipende solo dai parametri di input.
  • Con un linguaggio non funzionale, questo non è garantito, perché ogni funzione potrebbe avere effetti collaterali che influenzano il risultato delle altre funzioni.
risposta data 08.09.2018 - 22:08
fonte
5

Proverò a dare un esempio molto semplice:

def f():
    print("Hello")
    return 2

def g():
    print("World")
    return 3

print(f() + g())

Quale sarà il risultato?

Sarà o

Hello
World
5

o

World
Hello
5

in base all'ordine di di valutazione . In alcune lingue, verrà specificato che l'ordine di valutazione è da sinistra a destra, ad esempio (dando il primo risultato), ma in altri linguaggi non esiste un ordine di valutazione definito tranne quello implicitamente indicato dalle dipendenze dei dati ( cioè gli argomenti sono valutati prima della funzione, ma quale argomento viene valutato prima viene lasciato al compilatore). In quest'ultimo caso, non possiamo sapere quale dei due risultati otterremo.

Tuttavia, come puoi notare, sia nel mio esempio che nelle altre risposte, l'unica cosa che ci permette di osservare l'ordine delle operazioni sono effetti collaterali . I / O nel mio caso, mutazione nelle altre risposte. In un linguaggio puramente funzionale, non ci sono effetti collaterali . Ergo, l'ordine delle operazioni non può essere osservato.

Non è possibile osservare anche con quale frequenza si verifica! Un'implementazione potrebbe memoizzare il risultato di una valutazione e riutilizzarla, ad esempio:

f() + f()

può valutare f una o due volte (o anche un centinaio di volte) e non sarai in grado di distinguere.

    
risposta data 09.09.2018 - 02:30
fonte