Je suis d'accord avec Dietrich Epp: c'est une combinaison de plusieurs choses qui rendent le GHC rapide.
D'abord et avant tout, Haskell est de très haut niveau. Cela permet au compilateur d'effectuer des optimisations agressives sans casser votre code.
Pensez à SQL. Maintenant, quand j'écris une SELECT
déclaration, cela peut ressembler à une boucle impérative, mais ce n'est pas le cas . Il peut sembler qu'il boucle sur toutes les lignes de cette table en essayant de trouver celle qui correspond aux conditions spécifiées, mais en fait le "compilateur" (le moteur de base de données) pourrait faire une recherche d'index à la place - qui a des caractéristiques de performances complètement différentes. Mais parce que SQL est si haut niveau, le "compilateur" peut remplacer des algorithmes totalement différents, appliquer plusieurs processeurs ou canaux d'E / S ou des serveurs entiers de manière transparente, et plus encore.
Je pense que Haskell est le même. Vous pourriez penser que vous venez de demander à Haskell de mapper la liste d'entrée à une deuxième liste, de filtrer la deuxième liste en une troisième liste, puis de compter le nombre d'éléments obtenus. Mais vous n'avez pas vu GHC appliquer des règles de réécriture de fusion de flux dans les coulisses, transformant le tout en une seule boucle de code machine serrée qui fait tout le travail en un seul passage sur les données sans allocation - le genre de chose qui être fastidieux, sujet aux erreurs et non maintenable pour écrire à la main. Cela n'est vraiment possible qu'en raison du manque de détails de bas niveau dans le code.
Une autre façon de voir les choses pourrait être… pourquoi Haskell ne devrait-il pas être rapide? Que fait-il pour que ça ralentisse?
Ce n'est pas un langage interprété comme Perl ou JavaScript. Ce n'est même pas un système de machine virtuelle comme Java ou C #. Il compile jusqu'au code machine natif, donc pas de surcharge là-bas.
Contrairement aux langages OO [Java, C #, JavaScript…], Haskell a un effacement de type complet [comme C, C ++, Pascal…]. La vérification de tous les types se produit uniquement lors de la compilation. Il n'y a donc pas de vérification de type au moment de l'exécution pour vous ralentir non plus. (Aucune vérification de pointeur nul, d'ailleurs. En Java, par exemple, la JVM doit vérifier les pointeurs nuls et lever une exception si vous en déférencez une. Haskell n'a pas à s'embêter avec cette vérification.)
Vous dites qu'il semble lent de «créer des fonctions à la volée au moment de l'exécution», mais si vous regardez très attentivement, vous ne le faites pas vraiment. Cela pourrait vous ressembler , mais ce n'est pas le cas. Si vous dites (+5)
, eh bien, c'est codé en dur dans votre code source. Il ne peut pas changer au moment de l'exécution. Ce n'est donc pas vraiment une fonction dynamique. Même les fonctions au curry ne sont vraiment que la sauvegarde des paramètres dans un bloc de données. Tout le code exécutable existe réellement au moment de la compilation; il n'y a pas d'interprétation d'exécution. (Contrairement à certaines autres langues qui ont une "fonction eval".)
Pensez à Pascal. C'est vieux et personne ne l'utilise plus vraiment, mais personne ne se plaindrait que Pascal est lent . Il y a beaucoup de choses à ne pas aimer à ce sujet, mais la lenteur n'en fait pas vraiment partie. Haskell ne fait pas vraiment grand-chose de différent de Pascal, à part avoir un ramasse-miettes plutôt qu'une gestion manuelle de la mémoire. Et les données immuables permettent plusieurs optimisations au moteur GC [ce qui complique ensuite l'évaluation paresseuse].
Je pense que le truc c'est que Haskell a l' air avancé et sophistiqué et de haut niveau, et tout le monde pense "oh wow, c'est vraiment puissant, ça doit être incroyablement lent! " Mais ce n'est pas le cas. Ou du moins, ce n'est pas comme vous vous y attendez. Oui, il a un système de type incroyable. Mais tu sais quoi? Tout cela se produit au moment de la compilation. Au moment de l'exécution, c'est parti. Oui, cela vous permet de construire des ADT compliqués avec une ligne de code. Mais tu sais quoi? Un ADT n'est qu'un simple C ordinaire union
de l' struct
art. Rien de plus.
Le vrai tueur est l'évaluation paresseuse. Lorsque vous obtenez la rigueur / la paresse de votre code, vous pouvez écrire un code stupidement rapide qui est toujours élégant et beau. Mais si vous vous trompez, votre programme va des milliers de fois plus lentement , et il est vraiment difficile de comprendre pourquoi cela se produit.
Par exemple, j'ai écrit un petit programme trivial pour compter combien de fois chaque octet apparaît dans un fichier. Pour un fichier d'entrée de 25 Ko, le programme a pris 20 minutes à exécuter et a avalé 6 gigaoctets de RAM! C'est absurde!! Mais ensuite j'ai réalisé quel était le problème, j'ai ajouté un seul modèle de coup et le temps d'exécution est tombé à 0,02 seconde .
C'est là que Haskell va lentement et de façon inattendue. Et il faut certainement un certain temps pour s'y habituer. Mais au fil du temps, il devient plus facile d'écrire du code très rapide.
Qu'est-ce qui rend Haskell si rapide? Pureté. Types statiques. Paresse. Mais surtout, étant suffisamment élevé pour que le compilateur puisse changer radicalement l'implémentation sans casser les attentes de votre code.
Mais je suppose que c'est juste mon opinion ...