Linguaggi di programmazione funzionale più puri? [chiuso]


20

Sono interessato ad apprendere meglio la programmazione funzionale. Per fare ciò, sembra ovvio che dovrei costringermi ad usare il linguaggio di programmazione funzionale più puro possibile. Quindi, sto qui chiedendo, più o meno, un ordinamento di linguaggi di programmazione funzionale in base alla loro purezza.

Mi sembra che sarebbe più pratico imparare Lisp o Clojure (o Scheme, o Scala, ecc.), Ma per quello che ho sentito di recente, Haskell sarebbe molto difficile da insegnare a qualcuno a principi di programmazione funzionale. Non ne sono ancora sicuro, quindi ti chiedo: qual è il linguaggio di programmazione funzionale più puro? Un ordinamento sarebbe ottimo se molti fossero in competizione per il magnifico titolo del più puro linguaggio di programmazione funzionale.


2
Ho imparato Miranda all'università, quindi sono biasimato, ma consiglierei Haskel a chiunque voglia immergersi in un linguaggio funzionale senza le distrazioni dell'impurità . * 8 ')
Mark Booth,

2
Dopo aver appreso la programmazione funzionale, dovresti anche imparare la programmazione tipizzata staticamente con un sistema di tipo espressivo. Nella categoria combinata (sia funzionale che tipizzata), suggerisco: Coq> Haskell> OCaml> Scala> altri. Ci sono alcune alternative meno popolari che si inseriscono tra Coq e Haskell (come Epigram e Agda). Haskell manca il sistema di moduli espressivi di OCaml.
lukstafi,

Risposte:


28

Non esiste una scala per valutare il grado di purezza dei linguaggi funzionali. Se il linguaggio consente effetti collaterali è impuro, altrimenti è puro. Con questa definizione, Haskell, Mercury, Clean ecc sono linguaggi puramente funzionali; mentre Scala, Clojure, F #, OCaml ecc. sono quelli impuri.

EDIT: Forse avrei dovuto dirlo come "se il linguaggio non consente effetti collaterali senza far sapere al sistema dei tipi , è puro. Altrimenti è impuro".


6
Non proprio: Haskell consente effetti collaterali ( IOmonade). È solo che il codice che causa gli effetti collaterali è chiaramente contrassegnato come tale. Non penso che sia utile parlare di linguaggi puri / impuri (Haskell ti permette di programmare imperativamente! Gasp!), Ma può comunque essere utile parlare di blocchi di codice (funzione, modulo, programma, qualunque cosa) come puri /impuro.
Frank Shearar,

8
@Frank: Sì, Haskell consente effetti collaterali, ma fa molto di più che contrassegnare il codice che causa gli effetti collaterali. Mantiene anche la trasparenza referenziale anche quando si utilizza il codice che causa effetti collaterali ed è ciò che rende puro - almeno secondo la definizione di purezza di molte persone. Naturalmente ciò non corrisponde alla definizione di missingfaktor di "nessun effetto collaterale consentito".
sepp2k,

4
@Frank Shearar ma è utile parlare in questo modo perché la IOmonade è pura. È la libreria di runtime che non lo è, non il tuo codice, poiché la tua mainfunzione è sostanzialmente un enorme trasformatore di stato. (Qualcosa come main :: World -> Worlddietro le quinte)
alternativa il

1
Anche la mia lingua ha trasparenza referenziale. Basta scrivere program = "some C code"e quindi l'ambiente di runtime si occupa del codice C. :-)
Daniel Lubarov,

3
Poiché la stampa sullo schermo è un effetto collaterale, i programmi funzionali veramente puri sono piuttosto noiosi.

15

Poiché l'apprendimento è il tuo obiettivo e non scrivere programmi di per sé, non puoi ottenere più puro di Lambda Calculus .

Lambda Calculus esisteva prima dell'invenzione dei computer. Ci vollero diversi logici esperti che ci lavoravano per capire come fare la sottrazione (per un po 'fu teorizzato che erano possibili solo addizioni e moltiplicazioni).

Imparare come booleani e numeri e che ifpossono essere inventati da nulla sembra non mettere più gas nel tuo serbatoio, ma renderà il tuo serbatoio molto più grande.


Certo, probabilmente non c'è niente di più puro di così, ma stiamo attraversando un po 'troppo la barriera matematica. Voglio ancora esercitarmi nella programmazione e imparare un nuovo linguaggio assorbendo i principi funzionali. Tuttavia, concordo sul fatto che potrebbe essere interessante studiare il calcolo lambda solo per motivi di migliore comprensione dei fondamenti del paradigma funzionale (e di avere un serbatoio più grande).
Joanis,

2
Scrivere una prova è scrivere un programma e scrivere un programma sta scrivendo una prova. Quando apprenderai dell'isomorfismo Curry-Howard ti renderai conto di aver superato quella barriera matematica diecimila righe fa.
Macneil,

1
Non esiste una rappresentazione semplice di -1.
Daniel Lubarov,

2
@Mason Wheeler: λ-calculus da solo non ha numeri, o davvero nessun tipo di dato oltre alle astrazioni lambda. Se non diversamente specificato, chiunque parli di numeri nel calcolo λ probabilmente significa la codifica di Church per i numeri naturali , dove un numero n è rappresentato dalla composizione della funzione n-fold. Detto questo, io sono dubbia che era che una lotta molto per capire la sottrazione dopo che qualcuno in realtà ha provato; L'ho capito da solo in un pomeriggio, data solo la definizione di addizione e la consapevolezza che la sottrazione era possibile.
CA McCann,

1
La sottrazione usando i numeri della Chiesa è facile una volta che hai la scomposizione per casi. Costruire l'intero calcolo (derivato) per supportare numeri negativi, piuttosto altro lavoro.
Donal Fellows,

6

Le lingue impure non differiscono in linea di principio dalle lingue imperative più familiari, soprattutto ora che sono stati copiati molti trucchi funzionali. Ciò che differenzia è lo stile: come risolvi i problemi.

Sia che tu consideri Haskell puro o che la monade IO sia impurità, lo stile Haskell è una forma estrema di questo stile e vale la pena imparare.

La monade Haskell IO è derivata dalla teoria matematica delle (ovviamente) monadi. Tuttavia, per i programmatori imperativi, penso che abbia più senso un modo arretrato di arrivare alle monadi.

Fase uno - un linguaggio funzionale puro può facilmente restituire un grande valore di stringa come risultato. Questa grande stringa può essere il codice sorgente di un programma imperativo, derivato in modo funzionale da alcuni parametri che specificano i requisiti. È quindi possibile creare un compilatore di "livello superiore" che esegue il generatore di codice, quindi inserisce automaticamente quel codice generato nel compilatore del linguaggio imperativo.

Fase due: anziché generare codice sorgente testuale, si genera un albero di sintassi astratto tipizzato fortemente. Il compilatore in linguaggio imperativo viene assorbito nel compilatore "di livello superiore" e accetta l'AST direttamente come codice sorgente. Questo è molto più vicino a quello che fa Haskell.

Questo è ancora imbarazzante, però. Ad esempio, hai due tipi distinti di funzioni: quelle valutate durante la fase di generazione del codice e quelle eseguite quando viene eseguito il programma generato. È un po 'come la distinzione tra funzioni e modelli in C ++.

Quindi, per la fase 3, rendere le due uguali: la stessa funzione con la stessa sintassi può essere parzialmente valutata durante la "generazione del codice", oppure valutata completamente o non valutata affatto. Inoltre, scartare tutti i nodi AST dei costrutti di looping a favore della ricorsione. In effetti, scarta l'idea dei nodi AST come un tipo speciale di dati del tutto - non hanno nodi AST "valore letterale", solo valori ecc.

Questo è praticamente ciò che fa la monade IO: l'operatore di bind è un modo di comporre "azioni" per formare programmi. Non è niente di speciale - solo una funzione. Molte espressioni e funzioni possono essere valutate durante la "generazione del codice", ma quelle che dipendono dagli effetti collaterali I / O devono avere una valutazione ritardata fino al runtime - non da una regola speciale, ma come conseguenza naturale delle dipendenze dei dati in espressioni.

Le monadi in generale sono solo generalizzazioni - hanno la stessa interfaccia, ma implementano le operazioni astratte in modo diverso, quindi invece di valutare una descrizione del codice imperativo valutano invece qualcos'altro. Avere la stessa interfaccia significa che ci sono alcune cose che puoi fare alle monadi senza preoccuparti di quale monade, che risulta essere utile.

Questa descrizione farà sicuramente esplodere le teste dei puristi, ma per me spiega alcune delle vere ragioni per cui Haskell è interessante. Sfoca il confine tra programmazione e metaprogrammazione e utilizza gli strumenti della programmazione funzionale per reinventare la programmazione imperativa senza la necessità di una sintassi speciale.

Una critica che ho dei modelli C ++ è che sono una sorta di sublanguage funzionale puro rotto in un linguaggio imperativo - per valutare la stessa funzione di base in fase di compilazione piuttosto che in fase di runtime, è necessario implementarla nuovamente con uno stile completamente diverso di codifica. In Haskell, mentre l'impurità deve essere etichettata come tale nel suo tipo, la stessa identica funzione può essere valutata sia in senso meta-programmazione sia in senso non-meta-programmazione runtime nello stesso programma - non esiste una linea dura tra programmazione e metaprogrammazione.

Detto questo, ci sono alcune cose di metaprogrammazione che Haskell standard non può fare, fondamentalmente perché i tipi (e forse poche altre cose) non sono valori di prima classe. Tuttavia, ci sono varianti linguistiche che tentano di risolvere questo problema.

Molte cose che ho detto su Haskell possono essere applicate in linguaggi funzionali impuri - e talvolta anche in linguaggi imperativi. Haskell è diverso perché non hai altra scelta che adottare questo approccio: ti costringe sostanzialmente a imparare questo stile di lavoro. Puoi "scrivere C in ML", ma non puoi "scrivere C in Haskell" - almeno non senza imparare cosa succede sotto il cofano.


Grazie! Mi chiedevo se la genericità dei modelli C ++ avrebbe potuto essere unificata nelle funzioni stesse. Sembra che Haskell lo faccia, ma la parte importante è se questo può essere implementato in un linguaggio compilato , in modo tale che lo stesso motore che crea funzioni generiche dai modelli durante il tempo di compilazione, possa anche valutarli durante il runtime ...
Milind R

5

Personalmente categorizzo le lingue in tre livelli di purezza funzionale:

  • Linguaggi funzionali puri - ovvero quelli che trattano l'intero programma come una pura funzione e gestiscono la mutabilità esclusivamente attraverso l'interazione con il runtime - Haskell è probabilmente l'esempio canonico

  • Linguaggi funzionali impuri, ovvero quelli che enfatizzano uno stile funzionale ma consentono effetti collaterali. Clojure è chiaramente in questa categoria (consente la mutazione in modo controllato come parte del suo framework STM), anche OCaml o F #

  • Linguaggi multi-paradigma - questi non sono prima di tutto linguaggi funzionali ma possono supportare uno stile funzionale usando funzioni di prima classe ecc. Scala è un buon esempio qui, metterei anche Common Lisp in questa categoria e potresti anche includere lingue come JavaScript .

Nella tua situazione, suggerirei di imparare prima Haskell, poi Clojure. Questo è quello che ho fatto e ha funzionato molto bene per me! Haskell è bello e ti insegna i principi funzionali più puri, Clojure è molto più pragmatico e ti aiuta a fare molto mentre è ancora molto funzionale nel cuore.

Non considero davvero la terza categoria come linguaggi funzionali (anche se dopo aver appreso Haskell e Clojure mi trovo spesso a trarre vantaggio dalle tecniche funzionali quando li uso!)


Entro limiti molto severi , anche C ha capacità funzionali. (Il limite principale è la sintesi runtime di funzioni, che è davvero orribile in C, al punto che la maggior parte delle persone afferma che non si può fare.)
Donal Fellows

2
@Donal Fellows: nel limite in cui la stupidità va all'infinito, ogni linguaggio completo di Turing è funzionale: è sufficiente implementare un interprete Lisp nella lingua, e quindi usarlo. :]
CA McCann,

I paradigmi sono insignificanti se si considera che tutto è Turing completo e quindi può emulare tutto il resto. Quando pensi ai paradigmi devi concentrarti su ciò che il linguaggio rende idiomatico e su ciò che scoraggia / previene. Per essere considerato un linguaggio funzionale a mio avviso, le mutazioni e gli effetti collaterali devono essere unidiomatici (per FP impuri) o proibiti (per FP puri). Ciò significa che C non è funzionale. Né sono linguaggi multi-paradigma come Scala.
Mikera,

@mikera: Trovo la tua risposta molto interessante: se Scala non è funzionale ma solo multi-paradigma, come giudichi le funzionalità di programmazione funzionale in C ++ 11, C # o persino Java (l'introduzione pianificata di lambdas)?
Giorgio

@Giorgio: Penso che le caratteristiche funzionali in tutte le lingue che menzioni siano probabilmente una buona idea, semplicemente non le rendono "lingue funzionali". O per guardarlo dalla direzione opposta, il fatto che tu possa fare un IO monadico in stile imperativo non rende Haskell un linguaggio imperativo :-). I linguaggi multi-paradigma sono fondamentalmente il risultato quando si uniscono funzionalità di molti paradigmi diversi e non si mette prima di tutto uno stile particolare. IMHO C ++, C # e Java si stanno tutti muovendo verso l'essere multi-paradigma, ma non ci sono ancora perché OOP basato su classi è ancora dominante.
Mikera,

3

Se un linguaggio funzionale puro è tale, che ha solo funzioni pure (routine che non hanno effetti collaterali), allora è un po 'inutile, perché non può leggere input o scrivere output;)

Perché questo è davvero per l'apprendimento, penso che l'isolamento non sia necessariamente la strada da percorrere. La programmazione funzionale è un paradigma. È importante capire quali paradigmi sono adatti a quali problemi e, soprattutto, come possono essere combinati al meglio.

Lo dirò ora: le mode di programmazione sono stupide e controproducenti. Le uniche cose che contano sono che il tuo programma è breve, facile da scrivere, facile da mantenere e funziona correttamente. Il modo in cui lo realizzi non ha nulla a che fare con le mode di programmazione. - Richard Jones

A parte questo, se stai cercando "purezza" potresti voler dare un'occhiata a Pure . Si noti, tuttavia, l'estrema facilità di chiamare routine C rende funzionalmente impuro (ma anche molto potente).


3

Non è una risposta del tutto seria, ma Unlambda deve essere un contendente. Non è possibile ottenere più "puro funzionamento" dei combinatori SKI.


4
Follia! Eresia! Che tipo di linguaggio puro ha assurdità come combinatori con effetti collaterali? No Che cosa si vuole veramente qui è Lazy K .
CA McCann,

2

Erlang, Haskell, Scheme, Scala, Clojure e F #

Questa domanda sarebbe probabilmente migliore che vi aiuterà nel vostro ricerca come bene .


Grazie, domanda davvero interessante. Ho iniziato con PLT-Scheme / Racket, ma non ho ancora guardato SICP ... Real World Haskell sembra abbastanza interessante anche per me.
Joanis,

Lo schema incoraggia uno stile funzionale, ma ha set!(tra le altre cose) ...
Daniel Lubarov

Suggerisco OCaml, perché è il mio preferito. E ha un sistema di moduli, che Haskell e F # mancano.
lukstafi,

@lukstafi forse haskell è cambiato negli ultimi 3 anni (so che è cresciuto come un matto), ma ci sono sicuramente dei moduli in haskell.
Sara

@kai Per sistema di moduli intendevo i moduli parametrizzati da altri moduli (un "calcolo lambda" di moduli).
lukstafi,
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.