Domanda

Di recente ho imparato a conoscere la programmazione funzionale (in particolare Haskell, ma ho anche seguito tutorial su Lisp ed Erlang). Mentre ho trovato i concetti molto illuminanti, non vedo ancora il lato pratico del & Quot; nessun effetto collaterale & Quot; concetto. Quali sono i vantaggi pratici di esso? Sto cercando di pensare nella mentalità funzionale, ma ci sono alcune situazioni che sembrano troppo complesse senza la possibilità di salvare lo stato in modo semplice (non considero le monadi di Haskell "facili").

Vale la pena di continuare ad imparare Haskell (o un altro linguaggio puramente funzionale) in profondità? La programmazione funzionale o apolide è effettivamente più produttiva che procedurale? È probabile che in seguito continuerò a utilizzare Haskell o un altro linguaggio funzionale o dovrei impararlo solo per la comprensione?

Mi preoccupo meno delle prestazioni che della produttività. Quindi chiedo principalmente se sarò più produttivo in un linguaggio funzionale rispetto a un procedimento / orientato agli oggetti / qualunque cosa.

È stato utile?

Soluzione

Leggi Programmazione funzionale in breve .

Ci sono molti vantaggi nella programmazione senza stato, non ultimo il quale è drammaticamente codice multithread e simultaneo. Per dirla senza mezzi termini, lo stato mutevole è nemico del codice multithread. Se i valori sono immutabili per impostazione predefinita, i programmatori non devono preoccuparsi di un thread che muta il valore dello stato condiviso tra due thread, quindi elimina un'intera classe di bug multithreading relativi alle condizioni della competizione. Dal momento che non ci sono condizioni di gara, non c'è motivo di usare nemmeno i blocchi, quindi l'immutabilità elimina anche un'altra intera classe di bug relativi ai deadlock.

Questo è il motivo principale per cui conta la programmazione funzionale, e probabilmente il migliore per saltare sul treno di programmazione funzionale. Ci sono anche molti altri vantaggi, tra cui il debug semplificato (ovvero le funzioni sono pure e non mutano lo stato in altre parti di un'applicazione), un codice più conciso ed espressivo, un codice meno comprensivo rispetto alle lingue che sono fortemente dipendenti dai modelli di progettazione e il compilatore può ottimizzare il tuo codice in modo più aggressivo.

Altri suggerimenti

Più parti del tuo programma sono apolidi, più modi ci sono di unire le parti senza che si rompa nulla . Il potere del paradigma apolide non risiede nell'apolidia (o purezza) di per sé , ma nella capacità che ti dà di scrivere potenti funzioni riutilizzabili e combinarle.

Puoi trovare un buon tutorial con molti esempi nel documento di John Hughes Perché è importante la programmazione funzionale (PDF).

Sarai gobs più produttivo, specialmente se scegli un linguaggio funzionale che ha anche tipi di dati algebrici e corrispondenza dei modelli (Caml, SML, Haskell).

Molte delle altre risposte si sono concentrate sul lato prestazionale (parallelismo) della programmazione funzionale, che credo sia molto importante. Tuttavia, hai chiesto in modo specifico la produttività, come in, puoi programmare la stessa cosa più velocemente in un paradigma funzionale che in un paradigma imperativo.

In realtà trovo (per esperienza personale) che la programmazione in F # corrisponda al modo in cui penso meglio, quindi è più facile. Penso che sia la differenza più grande. Ho programmato sia in F # che in C #, e c'è molto meno & Quot; combattere la lingua & Quot; in F #, che adoro. Non devi pensare ai dettagli in F #. Ecco alcuni esempi di ciò che ho scoperto mi piace molto.

Ad esempio, anche se F # è tipizzato staticamente (tutti i tipi vengono risolti in fase di compilazione), l'inferenza del tipo determina quali tipi hai, quindi non devi dirlo. E se non riesce a capirlo, rende automaticamente la tua funzione / classe / qualunque cosa generica. Quindi non devi mai scrivere alcun generico, è tutto automatico. Trovo che ciò significhi che sto spendendo più tempo a pensare al problema e meno a come implementarlo. In effetti, ogni volta che torno a C #, trovo che mi manca davvero questa inferenza di tipo, non ti rendi mai conto di quanto sia fonte di distrazione fino a quando non devi più farlo.

Anche in F #, invece di scrivere loop, chiamate funzioni. È un cambiamento sottile, ma significativo, perché non devi più pensare al costrutto del ciclo. Ad esempio, ecco un pezzo di codice che passa attraverso e abbina qualcosa (non ricordo cosa, proviene da un puzzle di Eulero del progetto):

let matchingFactors =
    factors
    |> Seq.filter (fun x -> largestPalindrome % x = 0)
    |> Seq.map (fun x -> (x, largestPalindrome / x))

Mi rendo conto che fare un filtro quindi una mappa (che è una conversione di ogni elemento) in C # sarebbe abbastanza semplice, ma devi pensare a un livello inferiore. In particolare, dovresti scrivere il ciclo stesso e avere la tua istruzione if esplicita e quel tipo di cose. Da quando ho imparato F #, mi sono reso conto che ho trovato più facile programmare in modo funzionale, dove se vuoi filtrare scrivi & Quot; filter & Quot; e se vuoi mappare, tu scrivi " map " ;, invece di implementare ciascuno dei dettagli.

Adoro anche il | > operatore, che secondo me separa F # da ocaml, e forse altri linguaggi funzionali. È l'operatore pipe, ti consente di & Quot; pipe & Quot; l'output di un'espressione nell'input di un'altra espressione. Fa seguire il codice come penso di più. Come nello snippet di codice sopra, questo significa & Quot; prendi la sequenza dei fattori, filtrala, quindi mappala. & Quot; È un livello molto alto di pensiero, che non si ottiene in un linguaggio di programmazione imperativo perché sei così impegnato a scrivere il ciclo e le dichiarazioni if. È l'unica cosa che mi manca di più ogni volta che vado in un'altra lingua.

Quindi, in generale, anche se posso programmare sia in C # che in F #, trovo più facile usare F # perché puoi pensare ad un livello superiore. Direi che, poiché i dettagli più piccoli vengono rimossi dalla programmazione funzionale (almeno in F #), sono più produttivo.

Modifica : ho visto in uno dei commenti che hai chiesto un esempio di " state " in un linguaggio di programmazione funzionale. F # può essere scritto in modo imperativo, quindi ecco un esempio diretto di come puoi avere uno stato mutabile in F #:

let mutable x = 5
for i in 1..10 do
    x <- x + i

Considera tutti i bug difficili che hai impiegato a lungo nel debug.

Ora, quanti di questi bug erano dovuti a " interazioni indesiderate " tra due componenti separate di un programma? (Quasi tutti i bug di threading hanno questa forma: gare che coinvolgono la scrittura di dati condivisi, deadlock, ... Inoltre, è comune trovare librerie che abbiano un effetto inatteso sullo stato globale, o leggere / scrivere il registro / ambiente, ecc.) < em> I supporterebbe che almeno 1 su 3 "bug gravi" rientri in questa categoria.

Ora se passi alla programmazione senza stato / immutabile / pura, tutti questi bug scompaiono. Vengono invece presentate alcune nuove sfide (ad esempio quando si si desidera che moduli diversi interagiscano con l'ambiente), ma in un linguaggio come Haskell, tali interazioni vengono esplicitamente reificate nel sistema dei tipi, il che significa che si può semplicemente esaminare il tipo di funzione e la ragione del tipo di interazioni che può avere con il resto del programma.

Questa è la grande vittoria dell'IMO "immutabilità". In un mondo ideale, progetteremmo tutte fantastiche API e anche quando le cose fossero mutabili, gli effetti sarebbero locali e ben documentati e le interazioni "inattese" sarebbero ridotte al minimo. Nel mondo reale, ci sono molte API che interagiscono con lo stato globale in una miriade di modi, e questi sono la fonte dei bug più dannosi. Aspirare all'apolidia aspira a sbarazzarsi di interazioni non intenzionali / implicite / dietro le quinte tra i componenti.

Un vantaggio delle funzioni senza stato è che consentono il calcolo preliminare o la memorizzazione nella cache dei valori restituiti della funzione. Anche alcuni compilatori C consentono di contrassegnare esplicitamente le funzioni come apolidi per migliorarne l'ottimizzazione. Come molti altri hanno notato, le funzioni apolidi sono molto più facili da parallelizzare.

Ma l'efficienza non è l'unica preoccupazione. Una funzione pura è più facile da testare ed eseguire il debug poiché tutto ciò che la influenza viene esplicitamente dichiarato. E quando si programma in un linguaggio funzionale, si ha l'abitudine di creare il minor numero di funzioni & Quot; dirty & Quot; (con I / O, ecc.) possibile. Separare le cose con stato in questo modo è un buon modo per progettare programmi, anche in linguaggi non così funzionali.

I linguaggi funzionali possono richiedere del tempo per " ottenere " ;, ed è difficile da spiegare a qualcuno che non ha superato quel processo. Ma la maggior parte delle persone che perseverano abbastanza a lungo finalmente si rendono conto che ne vale la pena, anche se non usano molto i linguaggi funzionali.

Senza stato, è molto semplice parallelizzare automaticamente il codice (poiché le CPU sono realizzate con un numero sempre maggiore di core, questo è molto importante).

Qualche tempo fa ho scritto un post su questo argomento: Sull'importanza della purezza .

Le applicazioni Web senza stato sono essenziali quando si avvia un traffico maggiore.

Potrebbero esserci molti dati utente che non si desidera archiviare sul lato client per motivi di sicurezza, ad esempio. In questo caso è necessario memorizzarlo sul lato server. È possibile utilizzare la sessione predefinita delle applicazioni Web ma se si dispone di più di un'istanza dell'applicazione, è necessario assicurarsi che ciascun utente sia sempre indirizzato alla stessa istanza.

I sistemi di bilanciamento del carico spesso hanno la possibilità di avere "sessioni permanenti" in cui il sistema di bilanciamento del carico sa come a quale server inviare gli utenti. Questo non è l'ideale però, ad esempio significa che ogni volta che riavvii la tua applicazione web, tutti gli utenti connessi perderanno la sessione.

Un approccio migliore è quello di archiviare la sessione dietro i server Web in una sorta di archivio dati, al giorno d'oggi ci sono un sacco di fantastici prodotti nosql disponibili per questo (redis, mongo, elasticsearch, memcached). In questo modo i server Web sono senza stato ma si ha ancora il lato server di stato e la disponibilità di questo stato può essere gestita scegliendo la corretta configurazione del datastore. Questi archivi dati di solito hanno una grande ridondanza, quindi dovrebbe quasi sempre essere possibile apportare modifiche all'applicazione Web e persino all'archivio dati senza influire sugli utenti.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top