Le chiusure con effetti collaterali sono considerate "stile funzionale"?


9

Molti linguaggi di programmazione moderni supportano un concetto di chiusura , cioè di un pezzo di codice (un blocco o una funzione) che

  1. Può essere trattato come un valore, e quindi memorizzato in una variabile, passato a diverse parti del codice, può essere definito in una parte di un programma e invocato in una parte totalmente diversa dello stesso programma.
  2. Può acquisire variabili dal contesto in cui è definito e accedervi quando viene successivamente richiamato (possibilmente in un contesto totalmente diverso).

Ecco un esempio di una chiusura scritta in Scala:

def filterList(xs: List[Int], lowerBound: Int): List[Int] =
  xs.filter(x => x >= lowerBound)

La funzione letterale x => x >= lowerBoundcontiene la variabile libera lowerBound, che è chiusa (vincolata) dall'argomento della funzione filterListche ha lo stesso nome. La chiusura viene passata al metodo library filter, che può invocarlo ripetutamente come una normale funzione.

Ho letto molte domande e risposte su questo sito e, per quanto ho capito, il termine chiusura è spesso associato automaticamente alla programmazione funzionale e allo stile di programmazione funzionale.

La definizione di programmazione delle funzioni su Wikipedia recita:

Nell'informatica, la programmazione funzionale è un paradigma di programmazione che considera il calcolo come la valutazione di funzioni matematiche ed evita dati di stato e mutabili. Sottolinea l'applicazione di funzioni, in contrasto con lo stile di programmazione imperativo, che enfatizza i cambiamenti di stato.

e più avanti

[...] nel codice funzionale, il valore di output di una funzione dipende solo dagli argomenti immessi nella funzione [...]. L'eliminazione degli effetti collaterali può rendere molto più semplice la comprensione e la previsione del comportamento di un programma, che è una delle motivazioni chiave per lo sviluppo della programmazione funzionale.

D'altra parte, molti costrutti di chiusura forniti dai linguaggi di programmazione consentono a una chiusura di acquisire variabili non locali e di modificarle quando viene invocata la chiusura, producendo così un effetto collaterale sull'ambiente in cui sono state definite.

In questo caso, le chiusure implementano la prima idea di programmazione funzionale (le funzioni sono entità di prima classe che possono essere spostate come altri valori) ma trascurano la seconda idea (evitando effetti collaterali).

Questo uso di chiusure con effetti collaterali è considerato stile funzionale o le chiusure sono considerate un costrutto più generale che può essere utilizzato sia per uno stile di programmazione funzionale che non funzionale? C'è della letteratura su questo argomento?

NOTA IMPORTANTE

Non metto in dubbio l'utilità degli effetti collaterali o di avere chiusure con effetti collaterali. Inoltre, non sono interessato a una discussione sui vantaggi / svantaggi delle chiusure con o senza effetti collaterali.

Sono solo interessato a sapere se l'utilizzo di tali chiusure è ancora considerato stile funzionale dal proponente della programmazione funzionale o se, al contrario, il loro uso è scoraggiato quando si utilizza uno stile funzionale.


3
In uno stile / linguaggio puramente funzionale, gli effetti collaterali sono impossibili ... quindi credo che sarebbe una questione di: a quale livello di purezza si considera il codice funzionale?
Steven Evers,

@SnOrfus: al 100%?
Giorgio,

1
Gli effetti collaterali sono impossibili in un linguaggio puramente funzionale, quindi dovrebbe rispondere alla tua domanda.
Steven Evers,

@SnOrfus: in molte lingue le chiusure sono considerate un costrutto di programmazione funzionale, quindi ero un po 'confuso. Mi sembra che (1) il loro utilizzo sia più generale del semplice FP e (2) l'utilizzo di chiusure non garantisce automaticamente che si stia utilizzando uno stile funzionale.
Giorgio,

Il downvoter può lasciare una nota su come migliorare questa domanda?
Giorgio,

Risposte:


7

No; la definizione di paradigma funzionale riguarda la mancanza di stato e la mancanza implicita di effetti collaterali. Non si tratta di funzioni di alto ordine, chiusure, manipolazione di elenchi supportati dalla lingua o altre funzionalità della lingua ...

Il nome della programmazione funzionale deriva dalla nozione matematica di funzioni - le chiamate ripetute sullo stesso input danno sempre lo stesso output - funzione nullipotent. Ciò può essere ottenuto solo se i dati sono immutabili . Per facilitare lo sviluppo, le funzioni sono diventate mutabili (le funzioni cambiano, i dati sono ancora immutabili) e quindi la nozione di funzioni di ordine superiore (funzionali in matematica, ad esempio come derivati) - una funzione che accetta come input un'altra funzione. Affinché le funzioni possano essere portate in giro e passate come argomenti, sono state adottate funzioni di prima classe ; in seguito a questi, per migliorare ulteriormente la produttività, sono apparse chiusure .

Questa è, ovviamente, una visione molto semplificata.


3
Le funzioni di ordine superiore non hanno assolutamente nulla a che fare con la mutabilità, né le funzioni di prima classe. Posso passare in giro le funzioni e costruirne altre in Haskell con estrema facilità, senza alcuno stato mutevole né effetti collaterali.
martedì

@tdammers Probabilmente non ho espresso molto chiaramente; quando ho detto che le funzioni sono diventate mutabili, non mi riferivo alla mutabilità dei dati, ma al fatto che il comportamento di una funzione può essere modificato (da una funzione di alto ordine).
m3th0dman,

Una funzione di ordine superiore non deve modificare una funzione esistente; la maggior parte degli esempi di libri di testo combina le funzioni esistenti in nuove funzioni senza modificarle affatto map, ad esempio, che accetta una funzione, la applica a un elenco e restituisce un elenco dei risultati. mapnon modifica nessuno dei suoi argomenti, non cambia il comportamento della funzione che assume come argomento, ma è sicuramente una funzione di ordine superiore - se lo applichi parzialmente, con solo il parametro della funzione, hai costruito un nuova funzione che opera su un elenco, ma non è ancora avvenuta alcuna mutazione.
martedì

@tdammers: Esatto: la mutabilità è usata solo in linguaggi imperativi o multi-paradigma. Anche se questi possono avere il concetto di una funzione (o metodo) di ordine superiore e di una chiusura, rinunciano all'immutabilità. Questo può essere utile (non sto dicendo che uno non dovrebbe farlo) ma la mia domanda è se puoi ancora chiamarlo funzionale. Ciò significherebbe che, in generale, una chiusura non è un concetto strettamente funzionale.
Giorgio,

@Giorgio: la maggior parte delle lingue classiche FP fare avere mutevolezza; Haskell è l'unico a cui riesco a pensare dalla parte superiore della mia testa che non consente affatto la mutabilità. Tuttavia, evitare lo stato mutabile è un valore importante in FP.
martedì

9

No. "Stile funzionale" implica una programmazione priva di effetti collaterali.

Per capire perché, dai un'occhiata al post di blog di Eric Lippert sul ForEach<T>metodo di estensione e perché Microsoft non ha incluso un metodo di sequenza simile a quello in Linq :

Sono filosoficamente contrario a fornire un tale metodo, per due motivi.

La prima ragione è che ciò viola i principi di programmazione funzionale su cui si basano tutti gli altri operatori di sequenza. Chiaramente l'unico scopo di una chiamata a questo metodo è quello di causare effetti collaterali. Lo scopo di un'espressione è calcolare un valore, non causare un effetto collaterale. Lo scopo di un'affermazione è di causare un effetto collaterale. Il sito di chiamata di questa cosa assomiglierebbe molto a un'espressione (sebbene, ammettiamolo, dato che il metodo è di ritorno al vuoto, l'espressione potrebbe essere usata solo in un contesto di "espressione di espressione"). rendere l'unico e unico operatore di sequenza utile solo per i suoi effetti collaterali.

La seconda ragione è che ciò aggiunge zero nuovi poteri rappresentativi alla lingua. In questo modo puoi riscrivere questo codice perfettamente chiaro:

foreach(Foo foo in foos){ statement involving foo; }

in questo codice:

foos.ForEach((Foo foo)=>{ statement involving foo; });

che utilizza quasi esattamente gli stessi caratteri in un ordine leggermente diverso. Eppure la seconda versione è più difficile da capire, più difficile da eseguire il debug e introduce la semantica di chiusura, cambiando così potenzialmente la vita degli oggetti in modi sottili.


1
Anche se, sono d'accordo con lui ... il suo argomento si indebolisce un po 'quando si considera ParallelQuery<T>.ForAll(...). L'implementazione di un simile IEnumerable<T>.ForEach(...)è estremamente utile per le ForAlldichiarazioni di debug (sostituisci ForAllcon ForEache rimuovi il AsParallel()e puoi molto più facilmente eseguirne il debug / il debug)
Steven Evers

Chiaramente Joe Duffy ha idee diverse. : D
Robert Harvey,

Neanche Scala applica la purezza: puoi passare una chiusura non pura a una funzione di alto livello. La mia impressione è che l'idea di una chiusura non sia specifica per la programmazione funzionale ma è un'idea più generale.
Giorgio,

5
@Giorgio: le chiusure non devono essere pure per essere considerate chiusure. Devono essere puri, tuttavia, per essere considerati "Stile funzionale".
Robert Harvey,

3
@Giorgio: è davvero utile poter chiudere uno stato mutevole ed effetti collaterali e passarlo a un'altra funzione. È semplicemente contrario agli obiettivi della programmazione funzionale. Penso che molta confusione derivi dall'elegante supporto linguistico per i lambda che sono comuni nei linguaggi funzionali.
GlenPeterson,

0

La programmazione funzionale porta sicuramente le funzioni di prima classe al livello concettuale successivo, ma dichiarare funzioni anonime o passare funzioni ad altre funzioni non è necessariamente una cosa di programmazione funzionale. In C, tutto era un numero intero. Un numero, un puntatore a dati, un puntatore a una funzione ... tutto solo ints. Potresti passare i puntatori di funzione ad altre funzioni, creare elenchi di puntatori di funzione ... Diamine, se lavori nel linguaggio assembly, le funzioni sono in realtà solo indirizzi nella memoria in cui sono memorizzati blocchi di istruzioni della macchina. Dare un nome a una funzione è un sovraccarico extra per le persone che hanno bisogno di un compilatore per scrivere codice. Quindi le funzioni erano "di prima classe" in quel senso in un linguaggio completamente non funzionale.

Se l'unica cosa che fai è calcolare le formule matematiche in un REPL, allora puoi essere funzionalmente puro con la tua lingua. Ma la maggior parte della programmazione aziendale ha effetti collaterali. Perdere denaro in attesa del completamento di un programma di lunga durata è un effetto collaterale. Intraprendere qualsiasi azione esterna: scrivere su un file, aggiornare un database, registrare gli eventi in ordine, ecc. Richiede un cambio di stato. Potremmo discutere se lo stato è davvero cambiato se incapsuli queste azioni in involucri immutabili che eliminano gli effetti collaterali in modo che il tuo codice non debba preoccuparsene. Ma è come discutere se un albero fa rumore se cade nella foresta senza che nessuno lo ascolti. Il fatto è che l'albero iniziò in posizione verticale e finì per terra. Lo stato viene cambiato quando le cose vengono fatte, anche se solo per segnalare che le cose sono state fatte.

Quindi ci rimane una scala di purezza funzionale, non bianco e nero, ma sfumature di grigio. E su quella scala, minori sono gli effetti collaterali, minore è la mutabilità, migliore è (più funzionale).

Se hai assolutamente bisogno di effetti collaterali o stato mutevole nel tuo codice altrimenti funzionale, ti sforzi di incapsulare o dal resto del tuo programma il meglio che puoi. Usare una chiusura (o qualsiasi altra cosa) per iniettare effetti collaterali o stato mutevole in funzioni altrimenti pure è l'antitesi della programmazione funzionale. L'unica eccezione potrebbe essere se la chiusura fosse il modo più efficace per incapsulare gli effetti collaterali dal codice a cui è passato. Non è ancora "programmazione funzionale", ma potrebbe essere l'armadio che puoi trovare in determinate situazioni.

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.