Pourquoi est-il plus facile d'écrire un compilateur dans un langage fonctionnel? [fermé]

88

Je réfléchis à cette question depuis très longtemps, mais je n'ai vraiment pas trouvé de réponse sur Google ainsi qu'une question similaire sur Stackoverflow. S'il y a un doublon, je suis désolé pour cela.

Beaucoup de gens semblent dire que l'écriture de compilateurs et d'autres outils linguistiques dans des langages fonctionnels tels que OCaml et Haskell est beaucoup plus efficace et plus facile que de les écrire dans des langages impératifs.

Est-ce vrai? Et si oui, pourquoi est-il si efficace et facile de les écrire dans des langages fonctionnels plutôt que dans un langage impératif, comme C? De plus, un outil de langage dans un langage fonctionnel n'est-il pas plus lent que dans un langage de bas niveau comme C?

wvd
la source
5
Je ne dirais pas que c'est plus facile. Mais la nature fonctionnelle des tâches de compilation telles que l'analyse syntaxique se prête probablement tout naturellement à la programmation fonctionnelle. Les langages fonctionnels comme OCaml peuvent être extrêmement rapides, rivalisant avec la vitesse de C.
Robert Harvey
17
Mes amis, est-ce vraiment argumentatif? Quelqu'un a sûrement un aperçu. J'aimerais me connaître.
Robert Harvey
Je pense qu'il devrait y avoir au moins de bonnes raisons d'utiliser un langage fonctionnel plutôt qu'un langage impératif. J'ai trouvé un article sur le fait que les langages fonctionnels n'ont pas d'effets secondaires, etc. Mais ce n'était pas du tout clair. Cependant, si cela est argumentatif, il vaudrait peut-être mieux le fermer ou reformuler la question.
wvd
12
Est-il vraiment argumentatif de reconnaître que certaines niches sont mieux adaptées à un style de langage particulier? "Pourquoi C est meilleur que Javascript pour écrire des pilotes de périphériques" ne serait pas controversé, je pense ...
CA McCann
1
Je pensais que ce serait le contraire. Je lis "super petit compilateur" et il utilise des mutations variables partout.
Rolf

Réponses:

101

Souvent, un compilateur travaille beaucoup avec des arbres. Le code source est analysé dans un arbre de syntaxe. Cet arbre pourrait ensuite être transformé en un autre arbre avec des annotations de type pour effectuer la vérification de type. Vous pouvez maintenant convertir cet arbre en un arbre contenant uniquement des éléments de langage de base (conversion de notations syntaxiques de type sucre en une forme non sucrée). Vous pouvez maintenant effectuer diverses optimisations qui sont essentiellement des transformations sur l'arborescence. Après cela, vous créeriez probablement une arborescence sous une forme normale, puis vous parcourriez cette arborescence pour créer le code cible (assembleur).

Les langages fonctionnels ont des fonctionnalités telles que la correspondance de motifs et une bonne prise en charge de la récursivité efficace, ce qui facilite le travail avec les arbres, c'est pourquoi ils sont généralement considérés comme de bons langages pour écrire des compilateurs.

sepp2k
la source
Réponse la plus complète à ce jour, je la marquerai comme la réponse acceptée, mais je pense que la réponse de Pete Kirkham est également bonne.
wvd
1
Qu'en est-il de la «vérification de l'exactitude», puisque l'exactitude d'un compilateur est un attribut important, j'ai souvent entendu dire que les fans de langages fonctionnels incorporent d'une manière ou d'une autre une «preuve» d'exactitude dans leur flux de travail. Je n'ai aucune idée de ce que cela signifie vraiment en termes pratiques, mais comme la fiabilité du compilateur est importante, cela semble utile.
Warren P
3
@WarrenP: Le concept de "code porteur de preuve" provient de langages fonctionnels de typage statique. L'idée est que vous utilisez le système de types de manière à ce qu'une fonction puisse uniquement vérifier si elle est correcte, donc le fait que le code se compile est la preuve de l'exactitude. Bien sûr, ce n'est pas tout à fait possible tout en gardant la langue complète et la vérification de type décidable. Mais plus le système de type est fort, plus vous pouvez vous rapprocher de cet objectif.
sepp2k
3
La raison pour laquelle ce concept est principalement populaire dans la communauté fonctionnelle est que dans les langages avec un état mutable, vous devez également encoder des informations sur le moment et l'endroit où le changement d'état se produit dans les types. Dans les langages où vous savez que le résultat d'une fonction ne dépend que de ses arguments, il est beaucoup plus facile d'encoder une preuve dans les types (il est également beaucoup plus facile de prouver manuellement l'exactitude du code car vous n'avez pas à considérer quel état global est possible et comment cela affectera le comportement de la fonction). Cependant, rien de tout cela n'est spécifiquement lié aux compilateurs.
sepp2k
3
À mon avis, la caractéristique la plus importante est la correspondance de motifs. Optimiser un arbre de syntaxe abstrait avec la correspondance de modèles est stupidement facile. Le faire sans correspondance de modèles est souvent frustrant.
Bob Aman
38

De nombreuses tâches du compilateur sont des correspondances de modèles sur des structures arborescentes.

OCaml et Haskell ont tous deux des capacités de correspondance de modèles puissantes et concises.

Il est plus difficile d'ajouter une correspondance de modèle aux langages impératifs, car quelle que soit la valeur évaluée ou extraite pour correspondre au modèle doit être sans effet secondaire.

Pete Kirkham
la source
Cela semble être une réponse raisonnable, mais est-ce la seule chose? Par exemple, des choses telles que la récursivité de la queue joueraient-elles également un rôle?
wvd
Cela semble indiquer qu'il s'agit plus d'un problème du système de types que du modèle d'exécution réel. Quelque chose basé sur une programmation impérative avec des valeurs immuables sur des types structurels pourrait convenir.
Donal Fellows
@wvd: L'optimisation de la récursivité de la queue est un détail d'implémentation, pas une fonctionnalité de langage en tant que telle, qui rend les fonctions récursives linéaires équivalentes à une boucle itérative. Une fonction récursive pour parcourir une liste chaînée en C en bénéficierait tout autant que récurer sur une liste dans Scheme.
CA McCann
@wvd gcc C a une élimination des appels de queue, comme le font d'autres langages d'état mutables
Pete Kirkham
3
@camccann: Si le standard de langage garantit tco (ou au moins garantit que les fonctions récursives d'une certaine forme ne provoqueront jamais un débordement de pile ou une croissance linéaire de la consommation de mémoire), je considérerais cela comme une fonctionnalité du langage. Si le standard ne le garantit pas, mais que le compilateur le fait quand même, c'est une fonctionnalité du compilateur.
sepp2k
15

Un facteur important à considérer est qu'une grande partie de tout projet de compilateur est quand vous pouvez auto-héberger le compilateur et «manger votre propre nourriture pour chien». Pour cette raison, lorsque vous regardez des langages comme OCaml où ils sont conçus pour la recherche de langage, ils ont tendance à avoir des fonctionnalités intéressantes pour les problèmes de type compilateur.

Dans mon dernier travail de compilateur, nous avons utilisé OCaml exactement pour cette raison lors de la manipulation du code C, c'était juste le meilleur outil pour la tâche. Si les gens de l'INRIA avaient construit OCaml avec des priorités différentes, cela n'aurait peut-être pas été une si bonne solution.

Cela dit, les langages fonctionnels sont le meilleur outil pour résoudre n'importe quel problème, il s'ensuit donc logiquement qu'ils sont le meilleur outil pour résoudre ce problème particulier. QED.

/ me: revient à mes tâches Java un peu moins joyeusement ...

Ukko
la source
4
-1 pour "les langages fonctionnels sont le meilleur outil pour résoudre n'importe quel problème." Si c'était vrai, nous les utiliserions tous partout. ;)
Andrei Krotkov
15
@Andrei Krotkov: Le mot d'aujourd'hui est fa · ce · tious Prononciation: \ fə-ˈsē-shəs \ Fonction: adjectif Etymologie: moyen français facetieux, de facetie jest, du latin facetia Date: 1599 1: plaisanter ou plaisanter souvent de manière inappropriée : waggish <juste être facétieux> 2: censé être humoristique ou drôle: pas sérieux <a remarque facétieuse> synonymes voir spirituel En plus de manquer la blague, votre logique est toujours imparfaite. Vous supposez que tous les gens sont des acteurs rationnels, et que j'ai peur, ce n'est pas une hypothèse juste.
Ukko
5
Je suppose que j'ai raté la blague, car je connais des gens dans la vraie vie qui diraient à peu près la chose exacte, sauf sérieusement. La loi de Poe, je suppose. tvtropes.org/pmwiki/pmwiki.php/Main/PoesLaw
Andrei Krotkov
13
@Andrei: En utilisant votre argumentum ad populum : "Si la raison vaut mieux que l'ignorance émotionnelle, nous l'utilisons tous partout."
Tim Schaeffer
9

Fondamentalement, un compilateur est une transformation d'un ensemble de code à un autre - de la source à l'IR, de l'IR à l'IR optimisé, de l'IR à l'assemblage, etc. C'est précisément le genre de chose pour laquelle les langages fonctionnels sont conçus - une fonction pure est juste une transformation d'une chose à une autre. Les fonctions impératives n'ont pas cette qualité. Bien que vous puissiez écrire ce type de code dans un langage impératif, les langages fonctionnels sont spécialisés pour cela.

Mandrin
la source
6

Voir également

Modèle de conception F #

FP regroupe les choses «par opération», alors que OO regroupe les choses «par type» et «par opération» est plus naturel pour un compilateur / interpréteur.

Brian
la source
3
Cela concerne ce que l'on appelle, dans certains cercles de théorie des langages de programmation, le «problème de l'expression». Par exemple, voyez cette question , dans laquelle je montre un code Haskell vraiment horrible qui fait les choses à la manière des "types extensibles". Au contraire, forcer un langage POO dans le style "opérations extensibles" tend à motiver le modèle de visiteur.
CA McCann
6

Une possibilité est qu'un compilateur a tendance à avoir à traiter très soigneusement toute une série de cas secondaires. Obtenir le bon code est souvent facilité par l'utilisation de modèles de conception qui structurent l'implémentation de manière à mettre en parallèle les règles qu'elle implémente. Souvent, cela finit par être une conception déclarative (correspondance de modèles, «où») plutôt qu'impératif (séquençage, «quand») et donc plus facile à implémenter dans un langage déclaratif (et la plupart d'entre eux sont fonctionnels).

BCS
la source
4

On dirait que tout le monde a manqué une autre raison importante. Il est assez facile d'écrire un langage spécifique au domaine intégré (EDSL) pour les analyseurs qui ressemblent beaucoup à (E) BNF en code normal. Les combinateurs d'analyseurs comme Parsec sont assez faciles à écrire dans des langages fonctionnels en utilisant des fonctions d'ordre supérieur et une composition de fonctions. Non seulement plus facile mais très élégant.

Fondamentalement, vous représentez les analyseurs génériques les plus simples comme de simples fonctions et vous avez des opérations spéciales (généralement des fonctions d'ordre supérieur) qui vous permettent de composer ces analyseurs primitifs en analyseurs plus complexes et plus spécifiques pour votre grammaire.

Ce n'est bien sûr pas la seule façon d'analyser les frameworks.

snk_kid
la source