Programmazione asincrona in linguaggi funzionali


31

Sono principalmente un programmatore C / C ++, il che significa che la maggior parte della mia esperienza riguarda paradigmi procedurali e orientati agli oggetti. Tuttavia, come sanno molti programmatori C ++, nel corso degli anni C ++ ha spostato l'enfasi su uno stile funzionale, culminando infine con l'aggiunta di lambda e chiusure in C ++ 0x.

Indipendentemente da ciò, mentre ho una notevole esperienza nella codifica in uno stile funzionale usando C ++, ho pochissima esperienza con linguaggi funzionali reali come Lisp, Haskell, ecc.

Di recente ho iniziato a studiare questi linguaggi, perché l'idea di "nessun effetto collaterale" in linguaggi puramente funzionali mi ha sempre incuriosito, soprattutto per quanto riguarda le sue applicazioni alla concorrenza e al calcolo distribuito.

Tuttavia, proveniente da un background C ++, sono confuso su come questa filosofia "senza effetti collaterali" funzioni con la programmazione asincrona. Per programmazione asincrona intendo qualsiasi framework / API / stile di codifica che invia gestori di eventi forniti dall'utente per gestire eventi che si verificano in modo asincrono (al di fuori del flusso del programma). Ciò include librerie asincrone come Boost.ASIO o anche solo semplicemente la vecchia C gestori di segnali o gestori di eventi della GUI Java.

L'unica cosa che tutti questi hanno in comune è che la natura della programmazione asincrona sembra richiedere la creazione di effetti collaterali (stato), affinché il flusso principale del programma venga a conoscenza del fatto che è stato invocato un gestore di eventi asincrono. In genere, in un framework come Boost.ASIO, un gestore eventi modifica lo stato di un oggetto, in modo che l'effetto dell'evento venga propagato oltre la durata della funzione del gestore eventi. Davvero, cos'altro può fare un gestore di eventi? Non può "restituire" un valore al punto di chiamata, poiché non esiste un punto di chiamata. Il gestore di eventi non fa parte del flusso principale del programma, quindi l'unico modo in cui può avere un effetto sul programma effettivo è cambiare un certo stato (oppure longjmpin un altro punto di esecuzione).

Quindi sembra che la programmazione asincrona riguardi la produzione asincrona di effetti collaterali. Questo sembra completamente in contrasto con gli obiettivi della programmazione funzionale. In che modo questi due paradigmi sono riconciliati (in pratica) con linguaggi funzionali?


3
Caspita, stavo per scrivere una domanda come questa e non sapevo come metterla e poi l'ho vista nei suggerimenti!
Amogh Talpallikar,

Risposte:


11

Tutta la tua logica è solida, tranne per il fatto che penso che la tua comprensione della programmazione funzionale sia un po 'troppo estrema. Nel mondo reale la programmazione funzionale, proprio come la programmazione orientata agli oggetti o imperativa, riguarda la mentalità e il modo in cui affronti il ​​problema. Puoi ancora scrivere programmi nello spirito della programmazione funzionale mentre modifichi lo stato dell'applicazione.

In effetti, è necessario modificare lo stato dell'applicazione per fare effettivamente qualsiasi cosa. I ragazzi di Haskell ti diranno che i loro programmi sono "puri" perché racchiudono tutti i loro cambiamenti di stato in una monade. Tuttavia, i loro programmi interagiscono ancora con il mondo esterno. (Altrimenti qual è il punto!)

L'enfasi della programmazione funzionale "senza effetti collaterali" quando ha senso. Tuttavia, per eseguire la programmazione nel mondo reale, come hai detto, devi modificare lo stato del mondo. (Ad esempio, rispondere agli eventi, scrivere su disco e così via.)

Per ulteriori informazioni sulla programmazione asincrona in linguaggi funzionali, vi esorto caldamente a esaminare il modello di programmazione dei flussi di lavoro asincroni di F # . Ti permette di scrivere programmi funzionali nascondendo tutti i dettagli disordinati della transizione di thread all'interno di una libreria. (In un modo molto simile alle monadi in stile Haskell.)

Se il 'corpo' del thread calcola semplicemente un valore, quindi generare più thread e farli calcolare i valori in parallelo è ancora all'interno del paradigma funzionale.


5
Inoltre: guardare Erlang aiuta. Il linguaggio è molto semplice, puro (tutti i dati sono immutabili), ed è tutto circa l'elaborazione asincrona.
9000

Fondamentalmente dopo aver compreso i vantaggi dell'approccio funzionale e il cambiamento di stato solo quando è importante, si assicurerà automaticamente che anche se lavori in qualcosa come Java, sai quando modificare lo stato e come mantenere tali cose sotto controllo.
Amogh Talpallikar,

Non sono d'accordo - il fatto che il programma sia composto da funzioni "pure" non significa che non interagisca con il mondo esterno, è inteso che ogni funzione in un programma per un insieme di argomenti restituirà sempre lo stesso risultato, e questo è (purezza) un grosso problema, perché, dal punto di vista pratico, tale programma sarà meno difettoso, più "testabile", e l'esecuzione matematica delle funzioni potrebbe essere dimostrata matematicamente.
Gill Bates,

8

Questa è una domanda affascinante. L'opinione più interessante è, a mio avviso, l'approccio adottato in Clojure e spiegato in questo video:

http://www.infoq.com/presentations/Value-Identity-State-Rich-Hickey

Fondamentalmente la "soluzione" proposta è la seguente:

  • Scrivi la maggior parte del codice come funzioni "pure" classiche con strutture di dati immutabili e senza effetti collaterali
  • Gli effetti collaterali sono isolati tramite l'uso di riferimenti gestiti che controllano il cambiamento in base alle regole della memoria transazionale del software (ovvero tutti gli aggiornamenti allo stato mutabile avvengono all'interno di una transazione isolata appropriata)
  • Se prendi questa visione del mondo, puoi vedere "eventi" asincroni come trigger per un aggiornamento transazionale dello stato mutabile in cui l'aggiornamento è esso stesso una funzione pura.

Probabilmente non ho espresso l'idea con la stessa chiarezza degli altri, ma spero che questo dia l'idea generale - fondamentalmente sta usando un sistema STM concorrente per fornire il "ponte" tra la pura programmazione funzionale e la gestione asincrona degli eventi.


6

Una nota: un linguaggio funzionale è puro, ma il suo runtime non lo è.

Ad esempio, i runtime di Haskell coinvolgono code, thread multiplexing, garbage collection, ecc ... che non sono puri.

Un buon esempio è la pigrizia. Haskell supporta la valutazione pigra (che è l'impostazione predefinita, in realtà). Si crea un valore pigro preparando un'operazione, è quindi possibile creare più copie di questo valore ed è ancora "pigro" fintanto che non è necessario. Quando è necessario il risultato o se il tempo di esecuzione trova del tempo, il valore viene effettivamente calcolato e lo stato dell'oggetto pigro cambia per riflettere che non è più necessario eseguire il calcolo (ancora una volta) per ottenere il risultato. Ora è disponibile attraverso tutti i riferimenti, quindi lo stato dell'oggetto è cambiato, anche se è un linguaggio puro.


2

Sono confuso su come questa filosofia "senza effetti collaterali" funzioni con la programmazione asincrona. Per programmazione asincrona intendo ...

Questo sarebbe il punto, quindi.

Uno stile sonoro, senza effetti collaterali, è incompatibile con i framework che dipendono dallo stato. Trova un nuovo framework.

Lo standard WSGI di Python, ad esempio, non ci consente di creare applicazioni con effetti collaterali.

L'idea è che i vari "cambiamenti di stato" si riflettano in un ambiente di valori che possono essere costruiti in modo incrementale. Ogni richiesta è una pipeline di trasformazioni.


"non consente di creare applicazioni con effetti collaterali" Penso che manchi da qualche parte una parola.
Christopher Mahan,

1

Dopo aver appreso l'incapsulamento da Borland C ++ dopo aver appreso C, quando Borland C ++ mancava di modelli che consentivano la generica, il paradigma di orientamento degli oggetti mi rendeva inquieto. Un modo un po 'più naturale di calcolare sembrava filtrare i dati attraverso le pipe. Il flusso verso l'esterno aveva un'identità separata e indipendente dal flusso di input immutabile verso l'interno, piuttosto che essere considerato un effetto collaterale, cioè ogni fonte di dati (o filtro) era autonoma dagli altri. Il tasto premuto (un evento di esempio) ha vincolato le combinazioni di input utente asincrone ai codici chiave disponibili. Le funzioni operano su argomenti di parametri di input e lo stato incapsulato dalla classe è solo una scorciatoia per evitare il passaggio esplicito di argomenti ripetitivi tra un piccolo sottoinsieme di funzioni, oltre ad essere precisi nel contesto associato impedendo l'abuso di tali argomenti da qualsiasi funzione arbitraria.

L'adesione rigida a un particolare paradigma provoca disagi nell'affrontare astrazioni che perdono, ad es. runtime commerciali come JRE, DirectX, proponenti orientati agli oggetti target .net soprattutto. Per limitare l'inconveniente, le lingue optano per monadi accademicamente sofisticate come Haskell o per il supporto multi-paradigma flessibile come F # alla fine. A meno che l'incapsulamento non sia utile in alcuni casi d'uso dell'ereditarietà multipla, l'approccio multi-paradigma potrebbe essere un'alternativa superiore ad alcuni schemi di programmazione specifici, talvolta complessi, specifici del paradigma.

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.