Perché è più facile scrivere un compilatore in un linguaggio funzionale? [chiuso]


89

Ho pensato a questa domanda da molto tempo, ma davvero non sono riuscito a trovare la risposta su Google e una domanda simile su Stackoverflow. Se è presente un duplicato, mi dispiace.

Molte persone sembrano dire che scrivere compilatori e altri strumenti di linguaggio in linguaggi funzionali come OCaml e Haskell sia molto più efficiente e più facile che scriverli in linguaggi imperativi.

È vero? E se è così, perché è così efficiente e facile scriverli in linguaggi funzionali invece che in un linguaggio imperativo, come il C? Inoltre, uno strumento linguistico in un linguaggio funzionale non è più lento di un linguaggio di basso livello come il C?


5
Non direi che è più facile. Ma la natura funzionale dei compiti di compilazione come l'analisi probabilmente si presta abbastanza naturalmente alla programmazione funzionale. Linguaggi funzionali come OCaml possono essere estremamente veloci, rivaleggiando con la velocità di C.
Robert Harvey

17
Gente, è davvero polemico? Sicuramente qualcuno ha qualche intuizione. Mi piacerebbe conoscere me stesso.
Robert Harvey

Penso che dovrebbero esserci almeno alcuni buoni motivi per utilizzare un linguaggio funzionale rispetto a uno imperativo. Ho trovato un articolo che fondamentalmente parlava del fatto che i linguaggi funzionali non hanno effetti collaterali e simili. Ma non era affatto chiaro. Tuttavia, se questo è argomentativo, potrebbe essere meglio chiuderlo o riformulare la domanda.
wvd

12
È davvero polemico riconoscere che alcune nicchie sono più adatte a un particolare stile di linguaggio? "Perché C è meglio di Javascript per scrivere i driver di dispositivo" non sarebbe controverso, penso ...
CA McCann

1
Ho pensato che sarebbe stato il contrario. Sto leggendo "super minuscolo compilatore" e utilizza mutazioni variabili ovunque.
Rolf

Risposte:


102

Spesso un compilatore lavora molto con gli alberi. Il codice sorgente viene analizzato in un albero della sintassi. Quell'albero potrebbe quindi essere trasformato in un altro albero con annotazioni di tipo per eseguire il controllo del tipo. Ora potresti convertire quell'albero in un albero contenente solo elementi del linguaggio di base (convertendo notazioni sintattiche simili a zucchero in una forma non zuccherata). Ora potresti eseguire varie ottimizzazioni che sono fondamentalmente trasformazioni sull'albero. Dopodiché probabilmente creeresti un albero in una forma normale e poi itererai su quell'albero per creare il codice di destinazione (assembly).

I linguaggi funzionali hanno caratteristiche come il pattern-matching e un buon supporto per una ricorsione efficiente, che rendono facile lavorare con gli alberi, quindi è per questo che sono generalmente considerati buoni linguaggi per la scrittura di compilatori.


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

1
Che dire della "dimostrazione della correttezza", 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, questo sembra utile.
Warren P

3
@WarrenP: Il concetto di "codice porta-prove" deriva da linguaggi funzionali tipizzati staticamente. L'idea è di utilizzare il sistema di tipi in modo tale che una funzione possa solo verificare se è corretta, quindi il fatto che il codice venga compilato è la prova della correttezza. Ovviamente questo non è del tutto possibile mantenendo il turing della lingua completo e il controllo del tipo decidibile. Ma più forte è il sistema dei tipi, più ci si può avvicinare a quell'obiettivo.
sepp2k

3
Il motivo per cui questo concetto è principalmente popolare nella comunità funzionale è che nelle lingue con stato mutabile, dovresti anche codificare le informazioni su quando e dove si verifica il cambiamento di stato nei tipi. Nelle lingue in cui sai che il risultato di una funzione dipende solo dai suoi argomenti, è molto più facile codificare una dimostrazione nei tipi (è anche molto più facile provare manualmente la correttezza del codice perché non devi considerare quale stato globale è possibile e come influenzerà il comportamento della funzione). Tuttavia, niente di tutto questo è specificamente correlato ai compilatori.
sepp2k

3
La caratteristica più importante è il pattern matching secondo me. Ottimizzare un albero di sintassi astratto con il pattern matching è stupidamente facile. Farlo senza il pattern matching è spesso frustrante.
Bob Aman

38

Molte attività del compilatore sono la corrispondenza di modelli su strutture ad albero.

Sia OCaml che Haskell hanno capacità di corrispondenza dei modelli potenti e concise.

È più difficile aggiungere la corrispondenza del modello ai linguaggi imperativi poiché qualsiasi valore venga valutato o estratto per abbinare il modello deve essere privo di effetti collaterali.


Sembra una risposta ragionevole, ma è l'unica cosa? ad esempio, anche cose come la ricorsione della coda avrebbero un ruolo?
wvd

Ciò sembrerebbe indicare che si tratta più di un problema del sistema di tipi che del modello di esecuzione effettivo. Qualcosa basato sulla programmazione imperativa con valori immutabili rispetto ai tipi strutturali potrebbe andare bene.
Donal Fellows

@wvd: l'ottimizzazione della ricorsione in coda è un dettaglio di implementazione, non una caratteristica 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 vantaggio tanto quanto la ricorsività su una lista in Scheme.
CA McCann

@wvd gcc C ha l'eliminazione delle chiamate di coda, così come altre lingue di stato mutabili
Pete Kirkham

3
@camccann: se lo standard del linguaggio garantisce tco (o almeno garantisce che le funzioni ricorsive di una certa forma non causeranno mai un overflow dello stack o una crescita lineare del consumo di memoria), la considererei una caratteristica del linguaggio. Se lo standard non lo garantisce, ma il compilatore lo fa comunque, è una funzionalità del compilatore.
sepp2k

15

Un fattore importante da considerare è che una parte importante di qualsiasi progetto di compilatore è quando puoi auto-ospitare il compilatore e "mangiare il tuo cibo per cani". Per questo motivo, quando guardi linguaggi come OCaml, dove sono progettati per la ricerca linguistica, tendono ad avere ottime caratteristiche per problemi di tipo compilatore.

Nel mio ultimo lavoro da compilatore abbiamo usato OCaml esattamente per questo motivo mentre manipolavo il codice C, era semplicemente lo strumento migliore in circolazione per l'attività. Se i ragazzi dell'INRIA avessero costruito OCaml con priorità diverse, forse non sarebbe stato 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: ritorna alle mie attività Java un po 'meno gioiosamente ...


4
-1 per "i linguaggi funzionali sono lo strumento migliore per risolvere qualsiasi problema". Se fosse vero, li useremmo tutti ovunque. ;)
Andrei Krotkov

15
@Andrei Krotkov: la parola odierna del giorno è fa · ce · tious Pronuncia: \ fə-ˈsē-shəs \ Funzione: aggettivo Etimologia: facetieux francese medio, da facetie jest, da facetia latina Data: 1599 1: scherzare o scherzare spesso in modo inappropriato : waggish <solo essere scherzoso> 2: inteso per essere umoristico o divertente: non serio <a commento faceto> i sinonimi vedono spiritoso Oltre a perdere la battuta, la tua logica è ancora imperfetta. Stai assumendo che tutte le persone siano attori razionali e, temo, non è un presupposto equo.
Ukko

5
Immagino di essermi persa la battuta, poiché conosco persone nella vita reale che direbbero praticamente la cosa esatta, tranne che sul serio. La legge di Poe immagino. tvtropes.org/pmwiki/pmwiki.php/Main/PoesLaw
Andrei Krotkov

13
@Andrei: Usando il tuo argomento ad populum : "Se la ragione è meglio dell'ignoranza emotiva, la useremmo tutti ovunque".
Tim Schaeffer

9

Fondamentalmente, un compilatore è una trasformazione da un insieme di codice a un altro - da sorgente a IR, da IR a IR ottimizzato, da IR ad assembly, ecc. Questo è esattamente il tipo di cosa per cui sono progettati i linguaggi funzionali - una funzione pura è solo una trasformazione da una cosa all'altra. Le funzioni imperative non hanno questa qualità. Sebbene sia possibile scrivere questo tipo di codice in un linguaggio imperativo, i linguaggi funzionali sono specializzati per questo.


6

Guarda anche

Modello di progettazione F #

FP raggruppa le cose "per operazione", mentre OO raggruppa le cose "per tipo" e "per operazione" è più naturale per un compilatore / interprete.


3
Ciò si riferisce a quello che viene chiamato, in alcuni circoli di teoria del linguaggio di programmazione, il "problema dell'espressione". Ad esempio, vedi questa domanda , in cui dimostro un codice Haskell veramente orribile che fa le cose in modo "estensibile". Al contrario, forzare un linguaggio OOP nello stile "operazioni estensibili" tende a motivare il pattern del visitatore.
CA McCann

6

Una possibilità è che un compilatore tenda ad avere a che fare con molta attenzione con tutta una serie di casi d'angolo. Ottenere il codice corretto è spesso reso più semplice utilizzando modelli di progettazione che strutturano l'implementazione in modo parallelo alle regole che implementa. Spesso questo finisce per essere un design dichiarativo (pattern matching, "dove") piuttosto che imperativo (sequencing, "quando") e quindi più facile da implementare in un linguaggio dichiarativo (e la maggior parte di essi sono funzionali).


4

Sembra che tutti abbiano perso un altro motivo importante. È abbastanza facile scrivere un linguaggio specifico del dominio incorporato (EDSL) per parser che assomigliano molto a (E) BNF nel codice normale. Combinatori 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 anche molto elegante.

Fondamentalmente rappresentate i parser generici più semplici come semplici funzioni e avete operazioni speciali (in genere funzioni di ordine superiore) che vi consentono di comporre questi parser primitivi in ​​parser più complicati e più specifici per la vostra grammatica.

Questo non è l'unico modo per creare quadri parer, ovviamente.

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.