Concordo con Dietrich Epp: è una combinazione di diverse cose che rendono veloce GHC.
Innanzitutto, Haskell è di altissimo livello. Ciò consente al compilatore di eseguire ottimizzazioni aggressive senza rompere il codice.
Pensa a SQL. Ora, quando scrivo una SELECT
dichiarazione, potrebbe sembrare un ciclo imperativo, ma non lo è . Potrebbe sembrare che esegua il loop su tutte le righe di quella tabella cercando di trovare quella che soddisfa le condizioni specificate, ma in realtà il "compilatore" (il motore DB) potrebbe invece effettuare una ricerca dell'indice, che presenta caratteristiche di prestazione completamente diverse. Ma poiché SQL è di così alto livello, il "compilatore" può sostituire algoritmi totalmente diversi, applicare più processori o canali I / O o interi server in modo trasparente e altro ancora.
Penso a Haskell come lo stesso. Potresti pensare di aver appena chiesto a Haskell di mappare l'elenco di input su un secondo elenco, filtrare il secondo elenco in un terzo elenco e quindi contare il numero di elementi risultanti. Ma non hai visto GHC applicare le regole di riscrittura dello stream-fusion dietro le quinte, trasformando l'intera cosa in un singolo ciclo di codice macchina stretto che fa l'intero lavoro in un unico passaggio sui dati senza allocazione - il tipo di cosa che sarebbe essere noioso, soggetto a errori e non mantenibile per scrivere a mano. Questo è davvero possibile solo a causa della mancanza di dettagli di basso livello nel codice.
Un altro modo di vederlo potrebbe essere ... perché Haskell non dovrebbe essere veloce? Cosa fa che dovrebbe rallentarlo?
Non è un linguaggio interpretato come Perl o JavaScript. Non è nemmeno un sistema di macchina virtuale come Java o C #. Si compila fino al codice macchina nativo, quindi nessun sovraccarico lì.
A differenza dei linguaggi OO [Java, C #, JavaScript ...], Haskell ha la cancellazione completa del tipo [come C, C ++, Pascal ...]. Tutto il controllo del tipo avviene solo in fase di compilazione. Quindi non c'è neanche un controllo del tipo di run-time per rallentare. (Nessun controllo con puntatore null, del resto. In Java, per esempio, la JVM deve verificare la presenza di puntatori null e generare un'eccezione se ne deferisci uno. Haskell non deve preoccuparsi di quel controllo.)
Dici che suona lento "creare funzioni al volo in fase di esecuzione", ma se guardi con molta attenzione, in realtà non lo fai. Potrebbe sembrare che tu lo faccia, ma non lo fai. Se dici (+5)
, beh, è codificato nel tuo codice sorgente. Non può cambiare in fase di esecuzione. Quindi non è davvero una funzione dinamica. Anche le funzioni al curry in realtà stanno solo salvando i parametri in un blocco dati. Tutto il codice eseguibile esiste effettivamente in fase di compilazione; non esiste un'interpretazione di runtime. (A differenza di alcune altre lingue che hanno una "funzione di valutazione".)
Pensa a Pascal. È vecchio e nessuno lo usa più, ma nessuno si lamenterebbe che Pascal è lento . Ci sono molte cose che non piacciono, ma la lentezza non è davvero una di queste. Haskell non sta davvero facendo molto di diverso rispetto a Pascal, a parte la raccolta dei rifiuti piuttosto che la gestione manuale della memoria. E i dati immutabili consentono diverse ottimizzazioni al motore GC [che la valutazione lenta complica in qualche modo].
Penso che la cosa sia che Haskell abbia un aspetto avanzato, sofisticato e di alto livello, e tutti pensano "oh wow, questo è davvero potente, deve essere incredibilmente lento! " Ma non lo è. O almeno, non è come ti aspetteresti. Sì, ha un sistema di tipi sorprendente. Ma sai una cosa? Tutto ciò accade in fase di compilazione. Per run-time, è sparito. Sì, ti consente di costruire ADT complicati con una riga di codice. Ma sai una cosa? Un ADT è semplicemente una normale C union
di struct
s. Niente di più.
Il vero assassino è la valutazione pigra. Quando ottieni la rigidità / pigrizia del tuo codice giusto, puoi scrivere un codice stupidamente veloce che è ancora elegante e bello. Ma se sbagli questa roba, il tuo programma va migliaia di volte più lentamente , ed è davvero non ovvio il motivo per cui ciò sta accadendo.
Ad esempio, ho scritto un banale programma per contare quante volte ogni byte appare in un file. Per un file di input da 25 KB, l' esecuzione del programma ha richiesto 20 minuti e ha ingoiato 6 gigabyte di RAM! È assurdo !! Ma poi mi sono reso conto di quale fosse il problema, ho aggiunto un singolo modello di bang e il tempo di esecuzione è sceso a 0,02 secondi .
Questo è dove Haskell procede inaspettatamente lentamente. E ci vuole sicuramente un po 'di tempo per abituarsi. Ma col passare del tempo, diventa più facile scrivere codice molto veloce.
Cosa rende Haskell così veloce? Purezza. Tipi statici. Pigrizia. Ma soprattutto, essendo sufficientemente alto livello che il compilatore può cambiare radicalmente l'implementazione senza infrangere le aspettative del codice.
Ma credo sia solo la mia opinione ...