I linguaggi di programmazione funzionale non consentono effetti collaterali?


10

Secondo Wikipedia, i linguaggi di programmazione funzionale , che sono dichiarativi, non consentono effetti collaterali. Programmazione dichiarativa in generale, tenta di minimizzare o eliminare gli effetti collaterali.

Inoltre, secondo Wikipedia, un effetto collaterale è legato ai cambiamenti di stato. Quindi, i linguaggi di programmazione funzionale, in tal senso, eliminano effettivamente gli effetti collaterali, dal momento che non salvano alcuno stato.

Inoltre, un effetto collaterale ha un'altra definizione. Effetto collaterale

ha un'interazione osservabile con le sue funzioni di chiamata o il mondo esterno oltre a restituire un valore. Ad esempio, una particolare funzione potrebbe modificare una variabile globale o variabile statica, modificare uno dei suoi argomenti, sollevare un'eccezione, scrivere dati su un display o file, leggere dati o chiamare altre funzioni con effetti collaterali.

In questo senso, i linguaggi di programmazione funzionale in realtà consentono effetti collaterali, poiché ci sono innumerevoli esempi di funzioni che influenzano il loro mondo esterno, chiamando altre funzioni, sollevando eccezioni, scrivendo in file ecc.

Quindi, infine, i linguaggi di programmazione funzionale consentono effetti collaterali o no?

Oppure, non capisco cosa si qualifichi come "effetto collaterale", quindi le lingue imperative lo consentono e le dichiarazioni dichiarative no. Secondo quanto sopra e ciò che ottengo, nessun linguaggio elimina gli effetti collaterali, quindi o mi manca qualcosa sugli effetti collaterali o la definizione di Wikipedia è erroneamente ampia.

Risposte:


26

La programmazione funzionale include molte tecniche diverse. Alcune tecniche vanno bene con gli effetti collaterali. Ma un aspetto importante è il ragionamento equazionale : se chiamo una funzione sullo stesso valore, ottengo sempre lo stesso risultato. Quindi posso sostituire una chiamata di funzione con il valore restituito e ottenere un comportamento equivalente. Questo rende più facile ragionare sul programma, specialmente durante il debug.

Se la funzione ha effetti collaterali, questo non regge. Il valore restituito non equivale alla chiamata della funzione, poiché il valore restituito non contiene gli effetti collaterali.

La soluzione è smettere di usare gli effetti collaterali e codificare questi effetti nel valore restituito . Lingue diverse hanno sistemi di effetti diversi. Ad esempio, Haskell usa monadi per codificare alcuni effetti come IO o mutazione di stato. I linguaggi C / C ++ / Rust hanno un sistema di tipi che può impedire la mutazione di alcuni valori.

In un linguaggio imperativo, una print("foo")funzione stampa qualcosa e non restituisce nulla. In un linguaggio funzionale puro come Haskell, una printfunzione prende anche un oggetto che rappresenta lo stato del mondo esterno e restituisce un nuovo oggetto che rappresenta lo stato dopo aver eseguito questo output. Qualcosa di simile a newState = print "foo" oldState. Posso creare tutti i nuovi stati del vecchio stato che mi piacciono. Tuttavia, solo uno verrà mai utilizzato dalla funzione principale. Quindi ho bisogno di sequenziare gli stati da più azioni concatenando le funzioni. Per stampare foo bar, potrei dire qualcosa del genere print "bar" (print "foo" originalState).

Se non viene utilizzato uno stato di output, Haskell non esegue le azioni che portano a quello stato, perché è un linguaggio pigro. Al contrario, questa pigrizia è possibile solo perché tutti gli effetti sono esplicitamente codificati come valori di ritorno.

Si noti che Haskell è l' unico linguaggio funzionale comunemente usato che utilizza questa route. Altre lingue funzionali incl. la famiglia Lisp, la famiglia ML e i linguaggi funzionali più recenti come la Scala scoraggiano ma consentono ancora effetti collaterali - potrebbero essere chiamati linguaggi imperativi-funzionali.

L'uso degli effetti collaterali per l'I / O è probabilmente buono. Spesso, l'I / O (diverso dalla registrazione) viene eseguito solo al limite esterno del sistema. Nessuna comunicazione esterna avviene all'interno della logica aziendale. È quindi possibile scrivere il core del software in uno stile puro, pur eseguendo I / O impuri in una shell esterna. Ciò significa anche che il nucleo può essere apolide.

L'apolidia ha una serie di vantaggi pratici, come una maggiore ragionevolezza e scalabilità. Questo è molto popolare per i backend delle applicazioni web. Qualsiasi stato viene mantenuto all'esterno, in un database condiviso. Ciò semplifica il bilanciamento del carico: non devo incollare sessioni a un server specifico. E se avessi bisogno di più server? Basta aggiungerne un altro, perché utilizza lo stesso database. Cosa succede se un server si arresta in modo anomalo? Posso ripetere qualsiasi richiesta in sospeso su un altro server. Naturalmente, c'è ancora stato - nel database. Ma l'ho reso esplicito ed estratto, e potrei usare internamente un puro approccio funzionale se lo desidero.


Grazie per la risposta dettagliata Ciò che tengo a conclusione, sono che gli effetti collaterali non influiscono sul valore della funzione, a causa del ragionamento equazionale, ecco perché "i linguaggi funzionali non consentono / minimizzano gli effetti collaterali". Gli effetti incorporati nei valori delle funzioni influenzano e cambiano lo stato che viene mai salvato o salvato al di fuori del nucleo del programma. Inoltre, l'I / O avviene al limite esterno della logica aziendale.
codebot

3
@codebot, non proprio, secondo me. Se implementato correttamente, gli effetti collaterali nella programmazione funzionale dovrebbero riflettersi nel tipo di ritorno della funzione. Ad esempio, se una funzione potrebbe non riuscire (se un particolare file non esiste o se non è possibile stabilire una connessione al database), il tipo restituito della funzione dovrebbe incapsulare l'errore, anziché fare in modo che la funzione generi un'eccezione. Dai un'occhiata alla programmazione orientata alle ferrovie per un esempio ( fsharpforfunandprofit.com/posts/recipe-part2 ).
Aaron M. Eshbach

"... potrebbero essere chiamati linguaggi imperativo-funzionali": Simon Peyton Jones ha scritto "... Haskell è il miglior linguaggio di programmazione imperativo al mondo".
Giorgio,

5

Nessun linguaggio di programmazione elimina gli effetti collaterali. Penso che sia meglio dire che le lingue dichiarative contengono effetti collaterali mentre le lingue imperative no. Tuttavia, non sono così sicuro che qualcuno di questi discorsi sugli effetti collaterali ottenga la differenza fondamentale tra i due tipi di lingue e che sembra proprio quello che stai cercando.

Penso che aiuti a illustrare la differenza con un esempio.

a = b + c

La suddetta riga di codice potrebbe essere scritta praticamente in qualsiasi lingua, quindi come possiamo determinare se stiamo usando un linguaggio imperativo o dichiarativo? In che modo le proprietà di quella riga di codice differiscono nelle due classi di linguaggio?

In un linguaggio imperativo (C, Java, Javascript, ecc.) Quella riga di codice rappresenta semplicemente una fase di un processo. Non ci dice nulla sulla natura fondamentale di nessuno dei valori. Ci dice che al momento dopo questa riga di codice (ma prima della riga successiva) asarà uguale bpiù, cma non ci dice nulla ain senso lato.

In un linguaggio dichiarativo (Haskell, Scheme, Excel, ecc.) Quella riga di codice dice molto di più. Stabilisce una relazione invariante tra ae gli altri due oggetti in modo tale che sia sempre il caso che asia uguale a bpiù c. Si noti che ho incluso Excel nell'elenco delle lingue dichiarative perché anche se bo ccambia valore, rimarrà comunque il fatto che asarà uguale alla loro somma.

Secondo me questo , non gli effetti collaterali o lo stato, è ciò che rende diversi i due tipi di lingue. In un linguaggio imperativo, ogni particolare riga di codice non dice nulla sul significato generale delle variabili in questione. In altre parole, a = b + csignifica solo che per un brevissimo momento nel tempo, è acapitato di eguagliare la somma di be c.

Nel frattempo, nei linguaggi dichiarativi ogni riga di codice stabilisce una verità fondamentale che esisterà per tutta la durata del programma. In queste lingue, a = b + cti dice che qualunque cosa accada in qualsiasi altra riga di codice asarà sempre uguale alla somma di be c.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.