75

Ho pensato a questa domanda molto a lungo, ma davvero non riuscivo a trovare la risposta su Google e una domanda simile su Stackoverflow. Se c'è un duplicato, mi dispiace per quello.Perché scrivere un compilatore in un linguaggio funzionale è più semplice?

Un sacco di persone sembrano dire che scrivere compilatori e altri strumenti linguistici in linguaggi funzionali come OCaml e Haskell è molto più efficiente e più facile quindi scrivendoli in linguaggi imperativi.

È vero? E se è così - perché è così efficiente e facile da scrivere in lingue funzionali invece che in un linguaggio imperativo, come C? Inoltre, non è più lento uno strumento linguistico in una lingua funzionale, quindi in un linguaggio di basso livello come C?

+5

Non direi che è più facile. Ma la natura funzionale della compilazione di compiti come l'analisi probabilmente si presta in modo abbastanza naturale alla programmazione funzionale. I linguaggi funzionali come OCaml possono essere estremamente veloci, rivaleggiando con la velocità di C. –

+15

Folks, questo è davvero polemico? Sicuramente qualcuno ha qualche intuizione. Mi piacerebbe conoscere me stesso. –

+0

Penso che ci dovrebbero almeno essere delle buone ragioni per usare un linguaggio funzionale su un imperativo. Ho trovato un articolo che fondamentalmente è venuto giù su quei linguaggi funzionali senza effetti collaterali e così via. Ma non era affatto chiaro. Tuttavia, se questo è polemico, potrebbe essere meglio chiuderlo o riformulare la domanda. – wvd

risposta

91

Spesso un compilatore lavora molto con gli alberi. Il codice sorgente viene analizzato in un albero di sintassi. Quell'albero potrebbe quindi essere trasformato in un altro albero con annotazioni di tipo per eseguire il controllo dei tipi. Ora potreste convertire quell'albero in un albero contenente solo elementi del linguaggio di base (convertendo le notazioni sintattiche simili a zucchero in una forma coniugata). Ora puoi eseguire varie ottimizzazioni che sono fondamentalmente trasformazioni sull'albero. Dopodiché probabilmente creerai un albero in una forma normale e poi eseguirai un iterazione su quell'albero per creare il codice target (assembly).

linguaggio funzionale hanno caratteristiche come pattern-matching e un buon supporto per la ricorsione efficiente, che lo rendono facile lavorare con gli alberi, è per questo che stanno generalmente considerate buone lingue per la scrittura di compilatori.

+0

La risposta più completa finora, la segnerò come risposta accettata, tuttavia penso che la risposta di Pete Kirkham sia anche buona. – wvd

+1

Che dire di "prooving correctness", poiché la correttezza di un compilatore è un attributo importante, ho spesso sentito dire che i fan dei linguaggi funzionali incorporano in qualche modo una "prova" di correttezza nel loro flusso di lavoro. Non ho idea di cosa significhi realmente in termini pratici, ma poiché l'affidabilità del compilatore è importante, sembra che valga la pena. –

+3

@WarrenP: Il concetto di "proof-carrying code" deriva da linguaggi funzionali tipizzati staticamente. L'idea è che tu usi il sistema dei tipi in modo tale che una funzione possa digitare solo se è corretta, quindi il fatto che il codice compili è la prova della correttezza. Naturalmente questo non è completamente possibile pur mantenendo la lingua turing-completa e typechecking decidibile. Ma più forte è il sistema dei tipi, più ci si avvicina a questo obiettivo. – sepp2k

38

Un sacco di attività del compilatore sono la corrispondenza del modello sulle strutture ad albero.

Sia OCaml che Haskell dispongono di funzionalità di corrispondenza del modello potenti e concise.

È più difficile aggiungere la corrispondenza del modello alle lingue imperative, poiché qualsiasi valore valutato o estratto per adattarlo al modello deve essere privo di effetti collaterali.

+0

Sembra una risposta ragionevole, ma è questa l'unica cosa? per esempio. anche le cose come la ricorsione della coda hanno un ruolo? – wvd

+0

Ciò sembrerebbe indicare che si tratta più di un problema del sistema di tipi che del modello di esecuzione reale. Qualcosa basato su una programmazione imperativa con valori immutabili su tipi strutturali potrebbe andare bene. –

+0

@wvd: L'ottimizzazione della ricorsione in coda è un dettaglio di implementazione, non una funzionalità del linguaggio in quanto tale, che rende le funzioni ricorsive lineari equivalenti a un ciclo iterativo. Una funzione ricorsiva per percorrere una lista collegata in C ne trarrebbe beneficio, proprio come farebbe una ricorsione su una lista in Scheme. –

15

Un fattore importante da considerare è che una grande parte di qualsiasi progetto di compilazione è quando è possibile auto-ospitare il compilatore e "mangiare il proprio cibo per cani". Per questo motivo quando si guardano linguaggi come OCaml in cui sono progettati per la ricerca linguistica, tendono ad avere grandi funzionalità per problemi di tipo compilatore.

Nel mio ultimo lavoro compilatore abbiamo usato OCaml esattamente per questo motivo mentre manipolavo il codice C, era solo lo strumento migliore per l'attività. Se i ragazzi di INRIA avessero costruito OCaml con priorità diverse, non avrebbe potuto essere così adatto.

Detto questo, i linguaggi funzionali sono lo strumento migliore per risolvere qualsiasi problema, quindi ne consegue logicamente che sono lo strumento migliore per risolvere questo particolare problema. QED.

/me: esegue la scansione di nuovo ai miei compiti Java un po 'meno con gioia ...

+2

-1 per "le lingue funzionali sono lo strumento migliore per risolvere qualsiasi problema". Se questo fosse vero, li useremmo tutti ovunque. ;) –

+15

@Andrei Krotkov: parola di oggi del giorno è fa · CE · infettiva Pronuncia: \ fə-SE-shəs \ Funzione: aggettivo Etimologia: Medio facétieux francese, da facetie scherzo, dal latino facetia Data: 1599 1: scherzando o scherzando spesso a sproposito: waggish 2: vuole essere divertente o divertente: non gravi sinonimi vedere spiritoso in cima a mancare la battuta, la logica è ancora imperfetta . Stai assumendo che tutte le persone siano attori razionali, e che temo, non è una giusta ipotesi. – Ukko

+2

Immagino di essermi perso lo scherzo, poiché conosco persone nella vita reale che direbbero praticamente la stessa cosa, se non completamente seriamente. La legge di Poe, credo. http://tvtropes.org/pmwiki/pmwiki.php/Main/PoesLaw –

6

Vedi anche

F# design pattern

FP gruppi cose 'di funzionamento', mentre OO raggruppa le cose 'per tipo ', e' by operation 'è più naturale per un compilatore/interprete.

+3

Questo si riferisce a ciò che viene chiamato, in alcuni circoli di Teoria del linguaggio di programmazione, il "problema di espressione". Ad esempio, vedere [questa domanda] (http: // StackOverflow.it/questions/2807629 /), in cui mostro un codice Haskell veramente orribile che fa cose in modo "estensibile". Al contrario, forzare un linguaggio OOP nello stile delle "operazioni estendibili" tende a motivare il Pattern Visitatore. –

4

Sembra che a tutti sia sfuggito un altro motivo importante. È abbastanza facile scrivere un linguaggio specifico di dominio incorporato (EDSL) per i parser che assomigliano molto (E) a BNF nel codice normale.Combinatori di parser come Parsec sono abbastanza facili da scrivere in linguaggi funzionali utilizzando funzioni di ordine superiore e composizione di funzioni. Non solo più facile ma molto elegante.

Fondamentalmente si rappresentano le più semplici parser più generica, come solo le funzioni e si dispone di operazioni speciali (tipicamente funzioni di ordine superiore), che consentono di comporre questi parser primitive in più complicate, parser più specifici per la grammatica.

Questo non è l'unico modo per realizzare strutture parer ovviamente.

6

Fondamentalmente, un compilatore è una trasformazione da un set di codice a un altro - dalla sorgente all'IR, dall'IR all'IR ottimizzato, dall'IR all'assembly, ecc. Questo è esattamente il genere di linguaggio funzionale delle cose per cui è progettato - una pura funzione è solo una trasformazione da una cosa all'altra. Le funzioni imperative non hanno questa qualità. Sebbene tu possa scrivere questo tipo di codice in una lingua imperativa, le lingue funzionali sono specializzate per questo.

6

Una possibilità è che un compilatore tende ad avere a che fare con molta attenzione con tutta una serie di casi d'angolo. Ottenere il codice giusto è spesso reso più facile dall'uso di schemi di progettazione che strutturano l'implementazione in modo parallelo alle regole che implementa. Spesso questo finisce per essere un modello dichiarativo (pattern matching, "where") piuttosto che imperativo (sequencing, "when") e quindi più facile da implementare in un linguaggio dichiarativo (e la maggior parte di essi è funzionale).

Problemi correlati