I linguaggi di programmazione funzionale non consentono effetti collaterali?

8

Secondo wikipedia, Lingue di programmazione funzionale , che sono dichiarative, non consentono effetti collaterali. Programmazione dichiarativa in generale, tenta di minimizzare o eliminare gli effetti collaterali.

Inoltre, secondo wikipedia, un effetto collaterale è legato alle modifiche dello stato. Quindi, i linguaggi di programmazione funzionale, in questo senso, eliminano effettivamente gli effetti collaterali, poiché non salvano nessuno stato.

Ma, in aggiunta, un effetto collaterale ha un'altra definizione. Effetto collaterale

has an observable interaction with its calling functions or the outside world besides returning a value. For example, a particular function might modify a global variable or static variable, modify one of its arguments, raise an exception, write data to a display or file, read data, or call other side-effecting functions.

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, chiamano altre funzioni, sollevano eccezioni, scrivono in file ecc.

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

Oppure, non capisco cosa si qualifica come un "effetto collaterale", quindi le lingue imperative consentono loro e il Dichiarante 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.

Grazie

    
posta codebot 22.04.2018 - 00:01
fonte

2 risposte

23

La programmazione funzionale include molte tecniche diverse. Alcune tecniche vanno bene con effetti collaterali. Ma un aspetto importante è 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. Ciò rende più facile ragionare sul programma, specialmente quando si esegue il debug.

Se la funzione ha degli effetti collaterali, questo non vale. Il valore restituito non è equivalente alla chiamata alla funzione, poiché il valore restituito non contiene gli effetti collaterali.

La soluzione è smettere di usare effetti side e codificare questi effetti nel valore di ritorno . Diverse lingue hanno diversi sistemi di effetti. Per esempio. Haskell usa le monadi per codificare determinati effetti come la mutazione IO o stato. I linguaggi C / C ++ / Rust hanno un sistema di tipi che non consente la mutazione di alcuni valori.

In una lingua imperativa, una funzione print("foo") stamperà qualcosa e non restituirà nulla. In un linguaggio funzionale puro come Haskell, una funzione print accetta 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 tanti nuovi stati dal vecchio stato come mi piace. Tuttavia, solo uno sarà mai usato dalla funzione principale. Quindi ho bisogno di sequenziare gli stati da più azioni concatenando le funzioni. Per stampare foo bar , potrei dire qualcosa come print "bar" (print "foo" originalState) .

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

Si noti che Haskell è il linguaggio funzionale comunemente usato solo che utilizza questa rotta. Altre lingue funzionali incl. la famiglia Lisp, la famiglia ML e i nuovi linguaggi funzionali come Scala scoraggiano, ma consentono comunque effetti collaterali - potrebbero essere chiamati linguaggi imperativo-funzionali.

L'uso degli effetti collaterali per I / O probabilmente sta bene. Spesso, I / O (diverso dal logging) viene eseguito solo al limite esterno del sistema. Nessuna comunicazione esterna avviene all'interno della tua logica aziendale. È quindi possibile scrivere il nucleo del software in uno stile puro, mentre si esegue ancora l'I / O impuro in una shell esterna. Ciò significa anche che il core può essere stateless.

L'apolidia ha una serie di vantaggi pratici, come una maggiore ragionevolezza e scalabilità. Questo è molto popolare per i back-end delle applicazioni Web. Ogni stato è tenuto all'esterno, in un database condiviso. Ciò semplifica il bilanciamento del carico: non devo incollare le sessioni su un server specifico. Cosa succede se ho bisogno di più server? Basta aggiungere un altro, perché sta usando lo stesso database. Cosa succede se un server si arresta in modo anomalo? Posso ripetere qualsiasi richiesta in sospeso su un altro server. Certo, c'è ancora stato - nel database. Ma l'ho reso esplicito e l'ho estratto, e potrei usare internamente un approccio puramente funzionale se voglio.

    
risposta data 22.04.2018 - 00:44
fonte
2

Nessun linguaggio di programmazione elimina 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 nessuno di questi discorsi sugli effetti collaterali abbia la fondamentale differenza tra i due tipi di linguaggi e sembra davvero 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 linea di codice sono diverse nelle due classi di lingua?

In un linguaggio imperativo (C, Java, Javascript, & c.) quella riga di codice rappresenta semplicemente un passaggio in 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) a sarà uguale a b più c ma non ci dice nulla su a in senso ampio.

In un linguaggio dichiarativo (Haskell, Scheme, Excel, & c.) questa linea di codice dice molto di più. Stabilisce una relazione invariante tra a e gli altri due oggetti in modo che sia sempre nel caso in cui a sia uguale a b più c . Nota che ho incluso Excel nell'elenco delle lingue dichiarative perché anche se b o c cambia valore, il fatto resterà che a sarà uguale alla loro somma.

A mio parere questo , non gli effetti collaterali o lo stato, è ciò che rende i due tipi di lingue differenti. In un linguaggio imperativo, qualsiasi riga di codice specifica non ti dice nulla sul significato generale delle variabili in questione. In altre parole, a = b + c significa solo che per un brevissimo istante nel tempo, a è accaduto per eguagliare la somma di b e c .

Nel frattempo, nelle lingue dichiarative ogni riga di codice stabilisce una verità fondamentale che esisterà per l'intera vita del programma. In questi linguaggi, a = b + c ti dice che indipendentemente da ciò che accade in qualsiasi altra riga di codice a sarà sempre uguale alla somma di b e c .

    
risposta data 14.10.2018 - 03:03
fonte